Skip to content

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

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

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

Conversation

@ShashankFC

@ShashankFC ShashankFC commented Jan 5, 2026

Copy link
Copy Markdown
Collaborator

Test 7

Summary by CodeRabbit

Release Notes

  • New Features
    • Enhanced assignment synchronization with cycle prevention to ensure reliable bidirectional syncing between Sentry and integrated external issue tracking systems without infinite loops.

✏️ Tip: You can customize this high-level summary in your review settings.


Replicated from ai-code-review-evaluation/sentry-coderabbit#7

@ShashankFC

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 6, 2026

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 commented Jan 6, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request implements cycle-protection logic for assignment synchronization between Sentry and external integrations. A new AssignmentSource dataclass tracks the origin of assignments, enabling detection of recursive sync attempts. The source is propagated through the synchronization chain to prevent infinite loops when an assignment originates from the same integration.

Changes

Cohort / File(s) Summary
AssignmentSource dataclass definition
src/sentry/integrations/services/assignment_source.py
New frozen dataclass with source_name, integration_id, and queued fields. Includes from_integration() constructor and serialization methods to_dict() and from_dict() for round-trip conversion.
Integration mixin signatures
src/sentry/integrations/mixins/issues.py
Extended should_sync() methods on IssueBasicIntegration and IssueSyncIntegration to accept optional sync_source parameter. Added cycle-protection logic to return False when sync originates from the same integration. Extended sync_status_outbound() signature with optional assignment_source parameter.
Sync task and utility layer
src/sentry/integrations/tasks/sync_assignee_outbound.py, src/sentry/integrations/utils/sync.py
Updated sync_assignee_outbound() task to accept assignment_source_dict, parse it via AssignmentSource.from_dict(), and pass to integration methods. Extended sync_group_assignee_outbound() utility function with optional assignment_source parameter and serialization via to_dict().
Group assignee manager
src/sentry/models/groupassignee.py
Added optional assignment_source parameter to assign() and deassign() methods, propagating it through to sync_group_assignee_outbound() calls.
Test coverage
tests/sentry/integrations/services/test_assignment_source.py, tests/sentry/models/test_groupassignee.py
Added unit tests for AssignmentSource serialization/deserialization and integration tests validating cycle-protection behavior in assignment workflows.

Sequence Diagram

sequenceDiagram
    participant User
    participant Sentry as Sentry<br/>(Group Assignee)
    participant SyncUtil as Sync Utility
    participant Task as Sync Task
    participant Integration as Integration<br/>(Mixin)
    participant External as External<br/>Integration

    User->>Sentry: Assign group to user
    Sentry->>Sentry: Create AssignmentSource<br/>from Sentry origin
    Sentry->>SyncUtil: sync_group_assignee_outbound<br/>(assignment_source)
    SyncUtil->>Task: Queue sync_assignee_outbound<br/>(assignment_source_dict)
    Task->>Task: Parse assignment_source_dict
    Task->>Integration: should_sync()<br/>(sync_source)
    
    alt Same Integration (Cycle Detection)
        Integration-->>Integration: Detected: source from<br/>this integration
        Integration-->>Task: Return False
        Task-->>Sentry: ✓ Skip outbound sync
    else Different/No Source
        Integration-->>Integration: Allowed: safe to sync
        Integration-->>Task: Return True
        Task->>Integration: sync_assignee_outbound<br/>(assignment_source)
        Integration->>External: Create/Update assignment
        External-->>Integration: ✓ Acknowledged
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Round and round the syncs would go,
But now we've closed the loop's cascade flow!
With sources tracked from start to end,
No more infinite spins, dear friend—
Assignment cycles shall surely mend! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Implement cross-system issue synchronization' partially aligns with the changeset which adds cycle-protection logic to prevent recursive syncing, but overstates the scope by claiming 'implementation' when the changes are primarily about preventing sync cycles. Consider revising the title to be more specific about the cycle-protection feature, such as 'Add cycle-protection logic to prevent recursive issue synchronization' to better reflect the actual changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings

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

@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: 3

🤖 Fix all issues with AI Agents
In @src/sentry/integrations/services/assignment_source.py:
- Around line 14-18: The queued default is evaluated at import time causing all
AssignmentSource instances to share one timestamp; change the queued field to
use a per-instance factory: replace queued: datetime = timezone.now() with
queued: datetime = field(default_factory=timezone.now) and add the
dataclasses.field import (from dataclasses import dataclass, field) so each
AssignmentSource gets the current time when constructed.
- Around line 27-28: Update the AssignmentSource dataclass serialization: modify
to_dict() to convert the queued datetime field to an ISO 8601 string (e.g.,
queued.isoformat()) instead of returning a raw datetime via asdict(), and update
from_dict() to parse that ISO string back into a datetime (e.g.,
datetime.fromisoformat()) when reconstructing the object; ensure the change
covers the same key name "queued" so sync_assignee_outbound.apply_async()
receives a JSON-serializable dict.

In @tests/sentry/integrations/services/test_assignment_source.py:
- Line 13: Rename the misnamed test method test_from_dict_inalid_data to
test_from_dict_invalid_data in the test class so the name reflects "invalid"
(update the def declaration and any references to it, e.g., test discovery or
direct calls), then run the test suite to ensure no other references or typos
remain; ensure the new name uses the exact spelling test_from_dict_invalid_data.
🧹 Nitpick comments (1)
src/sentry/integrations/services/assignment_source.py (1)

30-35: Consider logging deserialization failures.

The method silently returns None on deserialization errors. While this may be intentional for graceful degradation, adding debug logging would improve observability when invalid data is encountered.

🔎 Optional improvement
+import logging
+
+logger = logging.getLogger(__name__)
+
 @classmethod
 def from_dict(cls, input_dict: dict[str, Any]) -> AssignmentSource | None:
     try:
         return cls(**input_dict)
     except (ValueError, TypeError):
+        logger.debug(
+            "Failed to deserialize AssignmentSource",
+            extra={"input_dict": input_dict},
+            exc_info=True,
+        )
         return None
📜 Review details

Configuration used: defaults

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
  • src/sentry/integrations/services/assignment_source.py
  • src/sentry/integrations/tasks/sync_assignee_outbound.py
  • src/sentry/integrations/utils/sync.py
  • src/sentry/models/groupassignee.py
  • tests/sentry/integrations/services/test_assignment_source.py
  • tests/sentry/models/test_groupassignee.py
🧰 Additional context used
🧬 Code graph analysis (6)
src/sentry/models/groupassignee.py (2)
src/sentry/integrations/services/assignment_source.py (1)
  • AssignmentSource (15-35)
src/sentry/integrations/utils/sync.py (1)
  • sync_group_assignee_outbound (123-145)
tests/sentry/models/test_groupassignee.py (2)
src/sentry/integrations/services/assignment_source.py (2)
  • AssignmentSource (15-35)
  • from_integration (21-25)
src/sentry/models/groupassignee.py (2)
  • GroupAssignee (249-287)
  • assign (130-199)
tests/sentry/integrations/services/test_assignment_source.py (1)
src/sentry/integrations/services/assignment_source.py (3)
  • AssignmentSource (15-35)
  • from_dict (31-35)
  • to_dict (27-28)
src/sentry/integrations/utils/sync.py (2)
src/sentry/integrations/services/assignment_source.py (3)
  • AssignmentSource (15-35)
  • from_integration (21-25)
  • to_dict (27-28)
src/sentry/models/groupassignee.py (3)
  • GroupAssignee (249-287)
  • deassign (201-245)
  • assign (130-199)
src/sentry/integrations/tasks/sync_assignee_outbound.py (3)
src/sentry/integrations/services/assignment_source.py (2)
  • AssignmentSource (15-35)
  • from_dict (31-35)
src/sentry/integrations/mixins/issues.py (2)
  • should_sync (66-67)
  • should_sync (382-394)
src/sentry/models/groupassignee.py (1)
  • assign (130-199)
src/sentry/integrations/mixins/issues.py (4)
src/sentry/integrations/services/assignment_source.py (1)
  • AssignmentSource (15-35)
src/sentry/integrations/jira/integration.py (1)
  • sync_status_outbound (990-1032)
src/sentry/integrations/example/integration.py (1)
  • sync_status_outbound (161-162)
src/sentry/integrations/jira_server/integration.py (1)
  • sync_status_outbound (1090-1134)
🪛 Ruff (0.14.10)
src/sentry/integrations/services/assignment_source.py

18-18: Do not perform function call timezone.now in dataclass defaults

(RUF009)

src/sentry/integrations/mixins/issues.py

66-66: Unused method argument: attribute

(ARG002)


66-66: Unused method argument: sync_source

(ARG002)

🔇 Additional comments (14)
src/sentry/integrations/utils/sync.py (2)

94-120: LGTM! Proper cycle prevention for inbound sync.

The inbound sync paths correctly create AssignmentSource from the integration for both assign and deassign operations. This enables downstream cycle detection by marking the assignment's origin, preventing infinite sync loops when the same integration processes the outbound sync.


123-145: LGTM! Outbound sync properly propagates assignment source.

The function signature is updated to accept an optional assignment_source parameter and correctly serializes it for the async task. The conditional serialization (assignment_source.to_dict() if assignment_source else None) properly handles both cases.

src/sentry/models/groupassignee.py (2)

130-199: LGTM! Assignment source properly threaded through assign flow.

The assign method correctly accepts an optional assignment_source parameter and forwards it to sync_group_assignee_outbound. The None default maintains backward compatibility with existing callers.


201-246: LGTM! Assignment source properly threaded through deassign flow.

The deassign method mirrors the assign method pattern, correctly accepting and forwarding the optional assignment_source parameter to enable cycle prevention in outbound sync.

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

120-178: LGTM! Test properly verifies default assignment_source behavior.

The test correctly validates that when no assignment_source is provided to assign, the outbound sync is called with assignment_source=None. The additional assertions thoroughly verify the assignment's side effects (database state, activity creation).


179-236: LGTM! Excellent test coverage for cycle prevention.

This test validates the core cycle-prevention logic: when an assignment originates from the same integration that would receive the outbound sync, the sync is correctly suppressed. The test verifies both the prevented sync (assert_not_called) and the successful local assignment, ensuring cycle prevention doesn't break normal functionality.


238-288: LGTM! Deassign test properly verifies default assignment_source behavior.

The test correctly validates that the deassign operation passes assignment_source=None to the outbound sync when no source is provided, maintaining consistency with the assign path.

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

30-61: LGTM! Task properly deserializes and propagates assignment source for cycle detection.

The task implementation correctly:

  1. Accepts assignment_source_dict as a serializable parameter for async task execution
  2. Deserializes it using AssignmentSource.from_dict with proper None handling
  3. Passes the parsed source to both should_sync (for cycle detection) and sync_assignee_outbound (for audit/context)

This completes the cycle-prevention chain, where should_sync checks if the source integration matches the target integration (per the mixins/issues.py implementation) and prevents the sync when they match.

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

1-12: LGTM: Clean imports with proper TYPE_CHECKING usage.

The TYPE_CHECKING block correctly avoids circular imports while maintaining type safety.


20-25: LGTM: Clean factory method.

The from_integration method correctly extracts the required fields from the integration object.

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

15-15: LGTM: Proper import of AssignmentSource.


66-67: LGTM: Base class signature updated for API consistency.

The added sync_source parameter maintains API consistency with the subclass override in IssueSyncIntegration. The static analysis warning about unused parameters is a false positive—this is the expected pattern for base class method signatures.


382-394: LGTM: Cycle detection logic correctly prevents sync loops.

The implementation properly checks if the assignment originates from the same integration and returns False to break the cycle, while still allowing other integrations to propagate changes. The null-safety is correct since org_integration is verified non-None before accessing integration_id.


411-418: Verify all implementations accept the new assignment_source parameter.

The signature change adds assignment_source: AssignmentSource | None = None as an optional parameter to the abstract method. Ensure all concrete implementations of sync_status_outbound in integration subclasses (Jira, Jira Server, GitHub, etc.) properly accept and propagate this parameter through their sync chains. Since the parameter is optional with a default value, backward compatibility depends on whether existing implementations use **kwargs or explicit parameter lists.

Comment on lines +14 to +18
@dataclass(frozen=True)
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 | 🔴 Critical

Critical: Fix mutable default using field(default_factory=...).

Using timezone.now() as a default value evaluates once at class definition time, meaning all AssignmentSource instances will share the same timestamp instead of capturing when each was actually created.

🔎 Proposed fix
-from dataclasses import asdict, dataclass
+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 = timezone.now()
+    queued: datetime = field(default_factory=timezone.now)
📝 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
@dataclass(frozen=True)
class AssignmentSource:
source_name: str
integration_id: int
queued: datetime = timezone.now()
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)
🧰 Tools
🪛 Ruff (0.14.10)

18-18: Do not perform function call timezone.now in dataclass defaults

(RUF009)

🤖 Prompt for AI Agents
In @src/sentry/integrations/services/assignment_source.py around lines 14 - 18,
The queued default is evaluated at import time causing all AssignmentSource
instances to share one timestamp; change the queued field to use a per-instance
factory: replace queued: datetime = timezone.now() with queued: datetime =
field(default_factory=timezone.now) and add the dataclasses.field import (from
dataclasses import dataclass, field) so each AssignmentSource gets the current
time when constructed.

Comment on lines +27 to +28
def to_dict(self) -> dict[str, Any]:
return asdict(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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the assignment_source.py file to understand the dataclass
head -50 src/sentry/integrations/services/assignment_source.py

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 220


🏁 Script executed:

# Search for to_dict() usage in the codebase
rg -n 'to_dict\(\)' --type=py | head -20

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 2364


🏁 Script executed:

# Check task/sync files that might use assignment_source
rg -l 'assignment_source' --type=py | grep -E '(task|sync|queue)'

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 175


🏁 Script executed:

# Look for datetime fields in assignment_source
rg -n 'datetime' src/sentry/integrations/services/assignment_source.py

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 154


🏁 Script executed:

# Check sync.py to see how assignment_source is used
cat -n src/sentry/integrations/utils/sync.py | head -100

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 4178


🏁 Script executed:

# Check sync_assignee_outbound.py
cat -n src/sentry/integrations/tasks/sync_assignee_outbound.py

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 3043


🏁 Script executed:

# Find where to_dict() is called on assignment_source before passing to task
rg -n -B 5 -A 5 'sync_assignee_outbound' src/sentry/integrations/ --type=py | head -80

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 6403


🏁 Script executed:

# Check the from_dict and to_dict implementations in assignment_source
cat -n src/sentry/integrations/services/assignment_source.py

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 1307


🏁 Script executed:

# Verify Celery's JSON serialization behavior - check if there's custom encoder
rg -n 'json_encoder\|JSONEncoder\|default=' src/sentry/ --type=py | grep -E '(celery|task)' | head -10

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Check if there are any successful test cases with assignment_source in Celery context
rg -n 'assignment_source.*to_dict\|sync_assignee_outbound' tests/ --type=py | head -20

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Check Celery configuration in settings
rg -n 'CELERY\|kombu\|json' src/sentry/conf/ --type=py | grep -i 'json\|serial' | head -15

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Look for tests that actually call sync_assignee_outbound with assignment_source
rg -n -B 3 -A 3 'sync_assignee_outbound\|assignment_source' tests/sentry/integrations/ --type=py | head -100

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Check if there's any isoformat or string conversion in from_dict or to_dict elsewhere
rg -n 'isoformat\|strftime\|fromisoformat\|fromtimestamp' src/sentry/integrations/ --type=py

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Check how other dataclasses handle to_dict with datetime fields
rg -n '@dataclass' src/sentry/ --type=py -A 10 | grep -B 5 -A 5 'datetime' | head -80

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 4851


🏁 Script executed:

# Look for custom serialization patterns in services
rg -n 'def to_dict\|def from_dict' src/sentry/services/ --type=py -A 3 | head -100

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Check the actual Celery setup to understand serialization
fd 'celery' src/sentry/conf/ --type py

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 251


🏁 Script executed:

# Check if datetime is actually causing issues in task parameters
rg -n 'apply_async.*dict' src/sentry/integrations/ --type=py -B 3 -A 1

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Check the sync.py file more carefully around the apply_async call
sed -n '120,150p' src/sentry/integrations/utils/sync.py

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 916


🏁 Script executed:

# Look for Celery configuration regarding datetime serialization
rg -n 'datetime\|timestamp' src/sentry/conf/settings/ --type=py | grep -i celery

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 147


🏁 Script executed:

# Check if there are any actual test failures or usage patterns for assignment_source in tasks
find tests -name '*.py' -exec grep -l 'assignment_source' {} \;

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 184


🏁 Script executed:

# See if there's a custom JSON encoder used for Celery
rg -n 'task_serializer\|result_serializer\|accept_content\|timezone' src/sentry/conf/ --type=py | head -20

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Check the assignment_source tests
cat -n tests/sentry/integrations/services/test_assignment_source.py

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 1488


🏁 Script executed:

# Check the groupassignee tests
cat -n tests/sentry/models/test_groupassignee.py | head -100

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 4931


🏁 Script executed:

# Find Celery configuration
find src/sentry -name 'celery.py' -o -name '*celery*.py' | head -5

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 249


🏁 Script executed:

# Look for where Celery kombu settings are configured
rg -n 'CELERY\|kombu_parser\|accept_content' src/sentry --type=py | head -20

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Check main celery configuration
cat -n src/sentry/celery.py | head -100

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 4151


🏁 Script executed:

# Check celery types
cat -n src/sentry/conf/types/celery.py

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 934


🏁 Script executed:

# Check if there's a custom datetime JSON encoder
rg -n 'JSONEncoder\|json.dumps\|json.loads' src/sentry/utils/celery.py -A 3

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Test if the issue actually occurs by looking at how apply_async handles datetime
rg -n 'apply_async' src/sentry/integrations/ --type=py -B 2 -A 2 | grep -A 5 'assignment_source'

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Check if there are integration tests that actually run sync_assignee_outbound with assignment_source
rg -n 'sync_assignee_outbound\|sync_group_assignee_outbound' tests/sentry/integrations/ --type=py | head -30

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Check Celery broker and serializer configuration
rg -n 'CELERY_TASK_SERIALIZER\|CELERY_ACCEPT_CONTENT\|broker\|serializer' src/sentry --type=py | head -20

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Verify that datetime in to_dict is problematic by checking if there's a custom encoder
python3 << 'EOF'
import json
from datetime import datetime

# Test if datetime is JSON serializable by default
try:
    test_dict = {"queued": datetime.now()}
    json.dumps(test_dict)
    print("datetime is JSON serializable")
except TypeError as e:
    print(f"datetime is NOT JSON serializable: {e}")
EOF

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 168


🏁 Script executed:

# Check what Python's json module does with datetime
python3 << 'EOF'
import json
from datetime import datetime

# Simulate what asdict does with datetime
test_dict = {
    "source_name": "test",
    "integration_id": 123,
    "queued": datetime.now()
}

# Try to serialize
try:
    json.dumps(test_dict)
    print("SUCCESS: datetime serialized")
except TypeError as e:
    print(f"FAILURE: {e}")
EOF

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 139


🏁 Script executed:

# Check if there's a custom Celery setup with JSON encoder
find src/sentry -name '*.py' -exec grep -l 'json_encoder\|JSONEncoder\|default=' {} \; | xargs grep -l 'celery\|task' | head -10

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 581


🏁 Script executed:

# Look at settings or configuration for Celery serializer
rg -n 'accept_content\|task_serializer\|result_serializer' src/sentry --type=py | head -20

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


🏁 Script executed:

# Check if there are any workarounds or custom serialization for datetime in the codebase
rg -n 'isoformat\|timestamp\|strptime\|fromisoformat' src/sentry/integrations --type=py

Repository: AI-Code-Review-Evals/coderabbit-sentry

Length of output: 64


Convert datetime to ISO format string in to_dict() for Celery serialization.

The to_dict() method includes the datetime object directly via asdict(), but Python's json module cannot serialize datetime objects. When sync_assignee_outbound.apply_async() passes this dict to Celery, task serialization will fail. Convert the queued field to an ISO format string in to_dict() and update from_dict() to deserialize it back to a datetime object.

🤖 Prompt for AI Agents
In @src/sentry/integrations/services/assignment_source.py around lines 27 - 28,
Update the AssignmentSource dataclass serialization: modify to_dict() to convert
the queued datetime field to an ISO 8601 string (e.g., queued.isoformat())
instead of returning a raw datetime via asdict(), and update from_dict() to
parse that ISO string back into a datetime (e.g., datetime.fromisoformat()) when
reconstructing the object; ensure the change covers the same key name "queued"
so sync_assignee_outbound.apply_async() receives a JSON-serializable dict.

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 | 🟡 Minor

Fix typo in test method name.

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

🔎 Proposed fix
-    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 misnamed test method test_from_dict_inalid_data to
test_from_dict_invalid_data in the test class so the name reflects "invalid"
(update the def declaration and any references to it, e.g., test discovery or
direct calls), then run the test suite to ensure no other references or typos
remain; ensure the new name uses the exact spelling test_from_dict_invalid_data.

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