Skip to content

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

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

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

Conversation

@everettbu

@everettbu everettbu commented Jul 26, 2025

Copy link
Copy Markdown

Test 7

Summary by CodeRabbit

  • New Features

    • Improved tracking of assignment changes by introducing assignment source metadata for issue and group assignee synchronization.
    • Enhanced control over assignment synchronization to prevent redundant updates when the source matches the integration.
  • Bug Fixes

    • Prevented unnecessary outbound synchronization when the assignment change originates from the same integration.
  • Tests

    • Added and updated tests to verify assignment source handling and synchronization behavior.

@coderabbitai

coderabbitai Bot commented Jul 26, 2025

Copy link
Copy Markdown

Walkthrough

This change introduces the AssignmentSource abstraction to track the origin of assignment actions for issues and groups, updating method signatures and logic across integration mixins, services, tasks, and models to propagate this context. New and updated tests verify the correct handling of assignment sources, including preventing redundant outbound syncs when the source matches the integration.

Changes

Cohort / File(s) Change Summary
AssignmentSource Abstraction
src/sentry/integrations/services/assignment_source.py
Introduces the AssignmentSource dataclass with methods for instantiation from integrations, serialization to/from dict, and tracking source metadata for assignments.
Integration Mixin Updates
src/sentry/integrations/mixins/issues.py
Updates should_sync and sync_status_outbound method signatures in IssueBasicIntegration and IssueSyncIntegration to accept an optional AssignmentSource. Adds logic to prevent outbound sync if the assignment source matches the integration, avoiding cycles.
Outbound Sync Task Update
src/sentry/integrations/tasks/sync_assignee_outbound.py
Modifies sync_assignee_outbound to accept and process an assignment_source_dict, converting it to an AssignmentSource and passing it to integration methods to maintain assignment source context throughout the sync process.
Sync Utility Updates
src/sentry/integrations/utils/sync.py
Adds assignment source tracking to inbound and outbound group assignee sync functions, propagating assignment source metadata and updating outbound sync task invocation to include serialized assignment source. Updates function signatures accordingly.
GroupAssignee Model Updates
src/sentry/models/groupassignee.py
Updates assign and deassign methods of GroupAssigneeManager to accept and propagate an optional AssignmentSource, forwarding it to outbound sync utilities.
AssignmentSource Tests
tests/sentry/integrations/services/test_assignment_source.py
Adds unit tests for AssignmentSource covering from_dict and to_dict methods, ensuring correct handling of valid and invalid input, and correct serialization behavior.
GroupAssignee Sync Tests
tests/sentry/models/test_groupassignee.py
Updates and adds tests to verify outbound sync behavior with assignment sources, including preventing redundant outbound sync when the assignment source matches the integration. Ensures assignment and activity records are correct in all scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A rabbit hops through fields of code,
Tracking sources as they goad.
Assignments now know whence they came,
Preventing cycles in the sync game.
With tests in tow and logic tight,
This patch ensures assignments are right!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ecosystem-sync-integration-after

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@everettbu

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2025

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/sentry/models/test_groupassignee.py (1)

211-212: Consider simplifying nested with statements.

The static analysis tool correctly identified that nested with statements can be combined for better readability.

Apply this diff to simplify the nested context managers:

-        with self.feature({"organizations:integrations-issue-sync": True}):
-            with self.tasks():
+        with self.feature({"organizations:integrations-issue-sync": True}), self.tasks():
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19c92a4 and f9919d7.

📒 Files selected for processing (7)
  • src/sentry/integrations/mixins/issues.py (4 hunks)
  • src/sentry/integrations/services/assignment_source.py (1 hunks)
  • src/sentry/integrations/tasks/sync_assignee_outbound.py (3 hunks)
  • src/sentry/integrations/utils/sync.py (4 hunks)
  • src/sentry/models/groupassignee.py (5 hunks)
  • tests/sentry/integrations/services/test_assignment_source.py (1 hunks)
  • tests/sentry/models/test_groupassignee.py (3 hunks)
🧰 Additional context used
🪛 Ruff (0.12.2)
tests/sentry/models/test_groupassignee.py

211-212: Use a single with statement with multiple contexts instead of nested with statements

(SIM117)

🔇 Additional comments (21)
tests/sentry/integrations/services/test_assignment_source.py (1)

7-39: Comprehensive test coverage for AssignmentSource.

The test class provides good coverage of the core functionality:

  • Edge case handling for empty and invalid data
  • Proper serialization/deserialization validation
  • Verification of all expected fields in the output dictionary

The tests properly validate that from_dict returns None for invalid inputs and correctly instantiates objects for valid data.

src/sentry/integrations/tasks/sync_assignee_outbound.py (2)

30-35: Good backward compatibility with optional parameter.

The addition of the optional assignment_source_dict parameter maintains backward compatibility while enabling the new assignment source tracking functionality.


53-61: Proper handling of assignment source deserialization.

The code correctly:

  • Deserializes the assignment source dictionary only when provided
  • Passes the parsed assignment source to both should_sync and sync_assignee_outbound
  • Handles the case where no assignment source is provided

The AssignmentSource.from_dict method will return None for invalid data, which is handled appropriately.

src/sentry/integrations/services/assignment_source.py (2)

21-25: Well-designed factory method.

The from_integration method provides a clean way to create AssignmentSource instances from integration objects, properly extracting the required fields.


31-35: Robust error handling in deserialization.

The from_dict method properly handles invalid input by catching both ValueError and TypeError exceptions and returning None, which aligns with the test expectations.

src/sentry/integrations/utils/sync.py (4)

96-99: Proper assignment source tracking in inbound sync.

The deassignment call correctly includes the assignment source derived from the integration, enabling proper tracking of the operation's origin.


112-116: Consistent assignment source propagation.

The assignment call properly includes the assignment source, maintaining consistency with the deassignment flow above.


123-128: Good API design for outbound sync.

The function signature extension with the optional assignment_source parameter maintains backward compatibility while enabling the new functionality.


137-145: Proper serialization for async task.

The code correctly:

  • Serializes the assignment source to a dictionary when provided
  • Passes None when no assignment source is available
  • Maintains all existing task parameters
src/sentry/models/groupassignee.py (4)

138-138: Good parameter addition for assignment tracking.

The optional assignment_source parameter maintains backward compatibility while enabling the new assignment source tracking functionality.


192-194: Proper propagation to sync utilities.

The assignment source is correctly passed through to the outbound sync function, ensuring the assignment origin is tracked throughout the sync pipeline.


207-207: Consistent parameter pattern in deassign.

The deassign method follows the same pattern as assign, maintaining API consistency.


238-240: Consistent sync integration in deassign.

The deassignment sync call properly includes the assignment source, maintaining consistency with the assignment flow.

tests/sentry/models/test_groupassignee.py (4)

7-7: LGTM!

The import of AssignmentSource is correctly added to support the new assignment source tracking functionality.


152-177: LGTM! Comprehensive test coverage for assignment with source tracking.

The test correctly verifies that:

  • sync_assignee_outbound is called with assignment_source=None
  • The assignment is properly created
  • Activity data is correctly recorded

The additional assertions strengthen the test coverage.


179-236: Excellent test for preventing sync loops.

This test properly validates the critical behavior where outbound sync is prevented when the assignment source matches the target integration, which is essential for avoiding infinite sync loops between integrations.

The test correctly:

  • Creates an integration and external issue
  • Uses AssignmentSource.from_integration(integration) as the source
  • Verifies sync_assignee_outbound is NOT called
  • Confirms the assignment and activity are still properly created

274-276: LGTM! Consistent parameter usage.

The test correctly includes assignment_source=None in the deassign test assertion, maintaining consistency with the updated method signature.

src/sentry/integrations/mixins/issues.py (4)

15-15: LGTM!

The import of AssignmentSource is correctly added to support the new assignment source tracking functionality.


66-67: LGTM! Consistent method signature update.

The base class method signature is correctly updated to accept the optional sync_source parameter, maintaining consistency with the concrete implementation.


382-394: Excellent implementation of sync loop prevention.

The logic correctly prevents sync cycles by:

  1. Checking if a sync_source is provided
  2. Comparing the source integration ID with the current integration ID
  3. Returning False to prevent sync when they match

This is a critical safety mechanism that allows other integrations to propagate changes while preventing an integration from syncing back to itself.

The implementation follows the fail-safe principle by only preventing sync when there's a definitive match, allowing normal sync behavior in all other cases.


411-418: LGTM! Method signature correctly updated.

The abstract method signature is properly updated to include the optional assignment_source parameter, which will be implemented by concrete integration classes to support assignment source tracking in status synchronization.

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.

⚠️ Potential issue

Fix mutable default argument issue.

Using timezone.now() as a default argument creates a mutable default that is evaluated at class definition time, not at instance creation time. This means all instances will have the same timestamp if created without explicitly providing a queued value.

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

Alternatively, use a factory function:

-    queued: datetime = timezone.now()
+    queued: datetime = field(default_factory=timezone.now)

You'll need to import field from dataclasses for the second approach.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
queued: datetime = timezone.now()
# at the top of src/sentry/integrations/services/assignment_source.py
from dataclasses import field
# inside the AssignmentSource dataclass
queued: datetime = field(default_factory=timezone.now)
🤖 Prompt for AI Agents
In src/sentry/integrations/services/assignment_source.py at line 18, the default
argument for the queued field is set to timezone.now(), which is evaluated once
at class definition, causing all instances to share the same timestamp. To fix
this, replace the default value with a default factory by importing field from
dataclasses and setting queued to field(default_factory=timezone.now) so that
timezone.now() is called each time a new instance is created.

result = AssignmentSource.from_dict(data)
assert result is None

def test_from_dict_inalid_data(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix typo in method name.

The method name has a typo: test_from_dict_inalid_data should be test_from_dict_invalid_data.

-    def test_from_dict_inalid_data(self):
+    def test_from_dict_invalid_data(self):
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_from_dict_inalid_data(self):
def test_from_dict_invalid_data(self):
🤖 Prompt for AI Agents
In tests/sentry/integrations/services/test_assignment_source.py at line 13,
rename the method from test_from_dict_inalid_data to test_from_dict_invalid_data
to correct the typo in the method name.

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