fix: track and subscribe inline assignees when work items are created via the external API#9450
Conversation
…ssue creation create_issue_activity only ran track_assignees when the payload contained assignee_ids (the web app field name). Work items created through the external REST API (/api/v1/...) carry assignees instead, so inline assignees on create produced no assignee activity and were never auto-subscribed to the work item - meaning they got no notification for the assignment or any later change. track_assignees itself already reads both keys via extract_ids, as does the update path (both field names are mapped); only the create gate was missing the external API key. Accept both assignee_ids and assignees in the create gate. Unit tests added for both payload shapes and the no-assignees case.
📝 WalkthroughWalkthroughChangesAssignee activity tracking
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/api/plane/tests/unit/bg_tasks/test_issue_activities_task.py (1)
110-117: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest the
Nonepayload scenario.To prevent regressions and ensure the background task handles null payloads gracefully without crashing, consider adding a test case where
requested_datais explicitlyNone.💡 Proposed test case
`@pytest.mark.django_db` def test_create_with_none_payload_creates_only_created_activity(self, workspace, project, issue, author): """Null payload: no crash, only 'created' activity is logged.""" activities = self._run(issue, project, workspace, author, None) assert activities == [] assert not IssueSubscriber.objects.filter(issue_id=issue.id).exists() assert IssueActivity.objects.filter(issue_id=issue.id, verb="created").exists()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/plane/tests/unit/bg_tasks/test_issue_activities_task.py` around lines 110 - 117, Add a test alongside test_create_without_assignees_only_creates_created_activity that calls _run with requested_data=None, then assert it completes without error, returns no additional activities, creates no IssueSubscriber record, and records only the created IssueActivity.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/plane/bgtasks/issue_activities_task.py`:
- Line 581: Update the assignee-tracking condition around requested_data to
first verify that requested_data is a dictionary before calling .get(). When it
is None or parses as a JSON array or other non-dictionary type, skip assignee
tracking without raising an AttributeError.
---
Nitpick comments:
In `@apps/api/plane/tests/unit/bg_tasks/test_issue_activities_task.py`:
- Around line 110-117: Add a test alongside
test_create_without_assignees_only_creates_created_activity that calls _run with
requested_data=None, then assert it completes without error, returns no
additional activities, creates no IssueSubscriber record, and records only the
created IssueActivity.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d7c696ee-8a38-4d59-b4c1-0cae7c81f18b
📒 Files selected for processing (2)
apps/api/plane/bgtasks/issue_activities_task.pyapps/api/plane/tests/unit/bg_tasks/test_issue_activities_task.py
| issue_activity.save(update_fields=["created_at", "actor_id"]) | ||
| requested_data = json.loads(requested_data) if requested_data is not None else None | ||
| if requested_data.get("assignee_ids") is not None: | ||
| if requested_data.get("assignee_ids") is not None or requested_data.get("assignees") is not None: |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Guard against non-dictionary types to prevent crashes.
If requested_data evaluates to None (which is explicitly allowed by the preceding line) or if it parses as a JSON array instead of a JSON object, calling .get() will raise an AttributeError and crash the background task.
Guard the dictionary access by verifying the payload type to ensure the task gracefully skips assignee tracking when the payload is missing or invalid.
🛡️ Proposed fix
- if requested_data.get("assignee_ids") is not None or requested_data.get("assignees") is not None:
+ if isinstance(requested_data, dict) and (requested_data.get("assignee_ids") is not None or requested_data.get("assignees") is not None):📝 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.
| if requested_data.get("assignee_ids") is not None or requested_data.get("assignees") is not None: | |
| if isinstance(requested_data, dict) and (requested_data.get("assignee_ids") is not None or requested_data.get("assignees") is not None): |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/api/plane/bgtasks/issue_activities_task.py` at line 581, Update the
assignee-tracking condition around requested_data to first verify that
requested_data is a dictionary before calling .get(). When it is None or parses
as a JSON array or other non-dictionary type, skip assignee tracking without
raising an AttributeError.
Description
create_issue_activityonly runstrack_assigneeswhen the create payload containsassignee_ids(the web app field name). Work items created through the external REST API (/api/v1/...) carryassigneesinstead, so inline assignees on create produce no assignee activity and are never auto-subscribed to the work item — meaning they get no notification for the assignment or for any later change. Assigning in a separatePATCHafterwards works, because the update path maps both field names totrack_assignees, andtrack_assigneesitself already reads both keys viaextract_ids(requested_data, "assignee_ids", "assignees"). Only the create gate misses the external API key.This PR accepts both
assignee_idsandassigneesin the create gate inapps/api/plane/bgtasks/issue_activities_task.py.Type of Change
Test Scenarios
Unit tests added in
plane/tests/unit/bg_tasks/test_issue_activities_task.py: creating withassignee_ids, creating withassignees(both must track the assignee activity and create theIssueSubscriber), and creating without assignees (no activities collected, no subscription, the "created" activity still recorded). Ran against PostgreSQL: 3 passed. Verified red/green: on the unpatched task exactly theassigneestest fails.ruff checkclean.References
Fixes #9449.
Summary by CodeRabbit