Skip to content

fix(api): add the documented work item relation removal endpoint - #9585

Open
mggarofalo wants to merge 2 commits into
makeplane:previewfrom
mggarofalo:fix/9584-work-item-relation-remove-endpoint
Open

fix(api): add the documented work item relation removal endpoint#9585
mggarofalo wants to merge 2 commits into
makeplane:previewfrom
mggarofalo:fix/9584-work-item-relation-remove-endpoint

Conversation

@mggarofalo

@mggarofalo mggarofalo commented Aug 11, 2026

Copy link
Copy Markdown

Description

The API reference documents a work-item relation removal endpoint:

POST /api/v1/workspaces/{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/relations/remove/
{"related_issue": "<uuid>"}

but that route was never registered, so it returns 404 on every instance. Relations can be created and listed through the public API and never removed, which leaves an integration able to build a dependency graph but unable to correct it. The only working path is the internal app API (POST /api/workspaces/.../issues/{id}/remove-relation/), which uses BaseSessionAuthentication and so rejects an X-API-Key — unusable server-to-server.

This registers the documented route so the docs page and the code agree.

What changed

  • apps/api/plane/api/views/issue.py — new IssueRelationRemoveAPIEndpoint. It consumes IssueRelationRemoveSerializer, which was already written with the exact request shape from the docs page but was never imported or referenced by anything.
  • apps/api/plane/api/urls/work_item.py — registers .../work-items/<issue_id>/relations/remove/ under the work-items prefix the docs advertise, next to the existing relations/ list/create route.
  • apps/api/plane/api/serializers/__init__.py, apps/api/plane/api/views/__init__.py — export the serializer and the endpoint.
  • apps/api/plane/app/views/issue/relation.pyIssueRelationViewSet.remove_relation called .first() and then .delete() with no None check, so removing a relation that does not exist raised AttributeError → HTTP 500. It now returns 404. The new public endpoint does not share this code path, but the bug is real on the app API and is fixed here rather than left behind.
  • apps/api/plane/tests/conftest.py (second commit) — ApiKeyRateThrottle counts requests per API key in the shared cache, and every contract test authenticates with the same token, so the history accumulated across the whole session. Once the suite crossed 60 requests inside a minute, whichever tests ran next were rate limited into 429s regardless of what they asserted — adding tests anywhere could break tests elsewhere, which is exactly what happened when the tests below were added. The api_key_client fixture now clears the token's throttle history so each test starts with the full budget.

Behaviour of the new endpoint

  • The relation is matched in either direction. blocked_by is stored once, so a work item on the blocking side has to match on related_issue_id rather than issue_id; the same request body works from either end.
  • Matching is scoped to the workspace, not the project, because relations may cross projects — the same scope the list/create endpoint uses.
  • The work item in the path must belong to the project in the path, otherwise membership of that project would not actually authorize removing relations of work items in projects the caller cannot see. A mismatch is a 404.
  • A relation that does not exist is a 404, not a 500.
  • Removal dispatches issue_relation.activity.deleted with notification=True, matching what the web app does. The stored relation is directional, so the relation type is reported as seen from the work item in the path (a stored blocked_by whose related_issue is the path item is reported as blocking) — otherwise the activity entries on the two work items read the wrong way round.
  • Returns 204 No Content with an empty body, as documented.

The OpenAPI schema is generated from the views, so the endpoint now appears in the generated reference under Work Item Relations with operation id remove_work_item_relation.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)

Test Scenarios

New contract tests in apps/api/plane/tests/contract/api/test_work_item_relations.py (9 tests, all passing):

  • removing a relation returns 204 and deletes it
  • a relation stored in the reverse direction is removable from either side
  • a relation that crosses projects is removable
  • a relation that does not exist returns 404 (this is the regression test for the .first().delete() crash)
  • a work item that does not belong to the project in the path returns 404 and leaves the relation intact
  • a missing related_issue returns 400
  • a malformed (non-UUID) related_issue returns 400
  • removal dispatches issue_relation.activity.deleted with notification=True and the relation type as seen from the path work item
  • a full round trip — create, list, remove, list — using only the documented public endpoints

Run with:

docker compose -f docker-compose-test.yml run --rm api-tests pytest plane/tests/contract/api/test_work_item_relations.py

The full suite passes — 525 tests, run twice back to back to confirm the throttle history no longer leaks between tests:

docker compose -f docker-compose-test.yml run --rm api-tests pytest plane/tests

The generated OpenAPI schema was checked too (ENABLE_DRF_SPECTACULAR=1 python manage.py spectacular): the endpoint appears at the documented path under the Work Item Relations tag with operationId: remove_work_item_relation, an IssueRelationRemoveRequest body and 204/400/401/403/404 responses, and adds no new schema warnings.

ruff check and ruff format are clean on every file touched.

References

Closes #9584

Summary by CodeRabbit

  • New Features

    • Added the ability to remove relationships between work items through the public API.
    • Supports relationships regardless of direction, with project and workspace validation.
    • Records activity when a relationship is removed.
  • Bug Fixes

    • Missing relationships now return a clear 404 error instead of proceeding with removal.

The API reference documents

  POST /api/v1/workspaces/{slug}/projects/{project_id}/work-items/{id}/relations/remove/

but the route was never registered, so it returned 404 everywhere.
Relations could be created and listed through the public API but never
removed, leaving integrations unable to correct a dependency graph. The
only working path was the internal app API, which uses session auth and
rejects an API key.

Register the documented route against a new IssueRelationRemoveAPIEndpoint,
which consumes the already-written but unreferenced
IssueRelationRemoveSerializer. The relation is matched in either direction
and scoped to the workspace, since relations may cross projects. The work
item in the path must belong to the project in the path, so membership of
that project actually authorizes the removal.

A relation that does not exist is a 404 rather than an AttributeError on
None -> 500. Fix the same latent crash in
IssueRelationViewSet.remove_relation on the app API.

Removal dispatches issue_relation.activity.deleted with notification=True,
reporting the relation type as seen from the work item in the path so the
activity feed on both work items reads the right way round.

Closes makeplane#9584
ApiKeyRateThrottle counts requests per API key in the shared cache, and
every contract test authenticates with the same token, so the history
accumulated across the session. Once the suite crossed 60 requests inside
a minute, whichever tests happened to run next were rate limited into 429s
regardless of what they asserted -- so adding tests anywhere could break
tests elsewhere.

Clear the token's throttle history in the api_key_client fixture so each
test starts with the full budget.
@CLAassistant

CLAassistant commented Aug 11, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 704c421b-0a39-4d12-9f9d-2604e0541496

📥 Commits

Reviewing files that changed from the base of the PR and between 1c8a60f and 012cab6.

📒 Files selected for processing (7)
  • apps/api/plane/api/serializers/__init__.py
  • apps/api/plane/api/urls/work_item.py
  • apps/api/plane/api/views/__init__.py
  • apps/api/plane/api/views/issue.py
  • apps/api/plane/app/views/issue/relation.py
  • apps/api/plane/tests/conftest.py
  • apps/api/plane/tests/contract/api/test_work_item_relations.py

📝 Walkthrough

Walkthrough

The PR adds the documented public API endpoint for removing work-item relations. It validates scope and input, supports relations stored in either direction, records deletion activity, handles missing relations with 404 responses, and adds contract coverage.

Changes

Work-item relation removal

Layer / File(s) Summary
Public removal route and exports
apps/api/plane/api/serializers/__init__.py, apps/api/plane/api/urls/work_item.py, apps/api/plane/api/views/__init__.py
The public serializer and endpoint exports now include relation removal. A POST-only /relations/remove/ route is registered.
Relation removal endpoint behavior
apps/api/plane/api/views/issue.py, apps/api/plane/app/views/issue/relation.py
The endpoint validates the related issue, checks project and workspace scope, removes direct or reverse relations, records deletion activity, and returns 204. Missing relations return 404.
Removal contract validation
apps/api/plane/tests/conftest.py, apps/api/plane/tests/contract/api/test_work_item_relations.py
Contract tests cover successful removal, reverse and cross-project relations, invalid input, missing relations, activity dispatch, and the complete API round trip. Test API-key throttling state is reset between tests.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant APIClient
  participant IssueRelationRemoveAPIEndpoint
  participant IssueRelationViewSet
  participant ActivityService
  APIClient->>IssueRelationRemoveAPIEndpoint: POST related_issue
  IssueRelationRemoveAPIEndpoint->>IssueRelationViewSet: Locate and remove relation
  IssueRelationViewSet-->>IssueRelationRemoveAPIEndpoint: Return deletion result
  IssueRelationRemoveAPIEndpoint->>ActivityService: Record deletion activity
  IssueRelationRemoveAPIEndpoint-->>APIClient: Return 204
Loading

Possibly related PRs

  • makeplane/plane#9531: Adds overlapping issue-relation removal behavior, missing-relation handling, and contract tests.
  • makeplane/plane#9397: Uses the same public serializer exports, work-item URL patterns, and issue view exports for another public API resource.

Suggested reviewers: dheeru0198

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the primary change: adding the documented work-item relation removal endpoint.
Description check ✅ Passed The description covers the change, type, testing, references, behavior, and validation results; omitted screenshots are not applicable to this API change.
Linked Issues check ✅ Passed The PR implements the documented endpoint, validates scope, supports reverse and cross-project relations, returns correct errors, and addresses the related 500 defect in issue #9584.
Out of Scope Changes check ✅ Passed All changes support issue #9584, including endpoint registration, error handling, test isolation, exports, and contract coverage.
Docstring Coverage ✅ Passed Docstring coverage is 82.35% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Warning

⚠️ This pull request shows signs of AI-generated slop (redundant_comments). It has been flagged by CodeRabbit slop detection and should be reviewed carefully.

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.

Docs document a work-item relation removal endpoint that does not exist in the API

2 participants