-
Notifications
You must be signed in to change notification settings - Fork 0
feat(ecosystem): Implement cross-system issue synchronization #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: ecosystem-sync-integration-before
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import asdict, dataclass | ||
| 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() | ||
|
|
||
| @classmethod | ||
| def from_integration(cls, integration: Integration | RpcIntegration) -> AssignmentSource: | ||
| return AssignmentSource( | ||
| source_name=integration.name, | ||
| integration_id=integration.id, | ||
| ) | ||
|
|
||
| def to_dict(self) -> dict[str, Any]: | ||
| return asdict(self) | ||
|
Comment on lines
+27
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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.pyRepository: 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 -20Repository: 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.pyRepository: 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 -100Repository: 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.pyRepository: 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 -80Repository: 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.pyRepository: 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 -10Repository: 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 -20Repository: 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 -15Repository: 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 -100Repository: 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=pyRepository: 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 -80Repository: 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 -100Repository: 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 pyRepository: 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 1Repository: 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.pyRepository: 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 celeryRepository: 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 -20Repository: 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.pyRepository: 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 -100Repository: 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 -5Repository: 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 -20Repository: AI-Code-Review-Evals/coderabbit-sentry Length of output: 64 🏁 Script executed: # Check main celery configuration
cat -n src/sentry/celery.py | head -100Repository: AI-Code-Review-Evals/coderabbit-sentry Length of output: 4151 🏁 Script executed: # Check celery types
cat -n src/sentry/conf/types/celery.pyRepository: 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 3Repository: 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 -30Repository: 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 -20Repository: 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}")
EOFRepository: 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}")
EOFRepository: 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 -10Repository: 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 -20Repository: 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=pyRepository: AI-Code-Review-Evals/coderabbit-sentry Length of output: 64 Convert The 🤖 Prompt for AI Agents |
||
|
|
||
| @classmethod | ||
| def from_dict(cls, input_dict: dict[str, Any]) -> AssignmentSource | None: | ||
| try: | ||
| return cls(**input_dict) | ||
| except (ValueError, TypeError): | ||
| return None | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,38 @@ | ||||||
| from typing import Any | ||||||
|
|
||||||
| from sentry.integrations.services.assignment_source import AssignmentSource | ||||||
| from sentry.testutils.cases import TestCase | ||||||
|
|
||||||
|
|
||||||
| class TestAssignmentSource(TestCase): | ||||||
| def test_from_dict_empty_array(self): | ||||||
| data: dict[str, Any] = {} | ||||||
| result = AssignmentSource.from_dict(data) | ||||||
| assert result is None | ||||||
|
|
||||||
| def test_from_dict_inalid_data(self): | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix typo in test method name. The method name contains a typo: 🔎 Proposed fix- def test_from_dict_inalid_data(self):
+ def test_from_dict_invalid_data(self):📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| data = { | ||||||
| "foo": "bar", | ||||||
| } | ||||||
|
|
||||||
| result = AssignmentSource.from_dict(data) | ||||||
| assert result is None | ||||||
|
|
||||||
| def test_from_dict_valid_data(self): | ||||||
| data = {"source_name": "foo-source", "integration_id": 123} | ||||||
|
|
||||||
| result = AssignmentSource.from_dict(data) | ||||||
| assert result is not None | ||||||
| assert result.source_name == "foo-source" | ||||||
| assert result.integration_id == 123 | ||||||
|
|
||||||
| def test_to_dict(self): | ||||||
| source = AssignmentSource( | ||||||
| source_name="foo-source", | ||||||
| integration_id=123, | ||||||
| ) | ||||||
|
|
||||||
| result = source.to_dict() | ||||||
| assert result.get("queued") is not None | ||||||
| assert result.get("source_name") == "foo-source" | ||||||
| assert result.get("integration_id") == 123 | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Fix mutable default using
field(default_factory=...).Using
timezone.now()as a default value evaluates once at class definition time, meaning allAssignmentSourceinstances will share the same timestamp instead of capturing when each was actually created.🔎 Proposed fix
📝 Committable suggestion
🧰 Tools
🪛 Ruff (0.14.10)
18-18: Do not perform function call
timezone.nowin dataclass defaults(RUF009)
🤖 Prompt for AI Agents