feat(api): create/list/retrieve/update workspaces via the token API#9400
feat(api): create/list/retrieve/update workspaces via the token API#9400clwluvw wants to merge 1 commit into
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughAdds public token-based workspace list, create, retrieve, and update APIs with serializer validation, membership-scoped access, slug generation, permission handling, routing, and contract tests. ChangesWorkspace token API
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant TokenClient
participant WorkspaceAPI
participant WorkspaceSerializer
participant WorkspaceDatabase
participant WorkspaceSeeder
TokenClient->>WorkspaceAPI: Request workspace list, creation, retrieval, or update
WorkspaceAPI->>WorkspaceDatabase: Scope access to active membership
WorkspaceAPI->>WorkspaceSerializer: Validate or serialize workspace data
WorkspaceSerializer-->>WorkspaceAPI: Return validated or serialized data
WorkspaceAPI->>WorkspaceDatabase: Persist workspace and creator membership
WorkspaceAPI->>WorkspaceSeeder: Dispatch workspace seeding
WorkspaceAPI-->>TokenClient: Return workspace response
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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.
Pull request overview
Adds a new public (API-token) workspace surface to allow headless provisioning and workspace management, mirroring key behaviors of the internal app workspace endpoints.
Changes:
- Introduces token-API endpoints to create/list/retrieve/patch workspaces, including auto-generated unique slugs and background seeding.
- Adds a full workspace serializer for the token API with name/slug validation aligned to the internal app serializer.
- Wires new workspace URL patterns with precedence handling and adds contract tests for the new endpoints and slug generator.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/api/plane/tests/contract/api/test_workspaces.py | Adds contract tests for workspace list/create/detail/patch and slug generation. |
| apps/api/plane/api/views/workspace.py | Implements token-API workspace endpoints plus unique slug generator and annotated queryset. |
| apps/api/plane/api/views/init.py | Exports the new workspace API views. |
| apps/api/plane/api/urls/workspace.py | Adds URL patterns for /workspaces/ and /workspaces/{slug}/. |
| apps/api/plane/api/urls/init.py | Registers workspace URLs ahead of router-mounted conflicting paths. |
| apps/api/plane/api/serializers/workspace.py | Adds WorkspaceSerializer (token API) mirroring internal validation rules. |
| apps/api/plane/api/serializers/init.py | Exposes WorkspaceSerializer from the serializers package. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
apps/api/plane/api/serializers/workspace.py (1)
43-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
logo_urlis redundantly listed inread_only_fields.
logo_urlis already declared asserializers.CharField(read_only=True)on line 44, so its entry inMeta.read_only_fieldson line 79 is a no-op. Remove it fromread_only_fieldsto avoid confusion.♻️ Remove redundant entry
read_only_fields = [ "id", "created_by", "updated_by", "created_at", "updated_at", "owner", - "logo_url", ]Also applies to: 79-79
🤖 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/api/serializers/workspace.py` around lines 43 - 45, Remove logo_url from the serializer’s Meta.read_only_fields declaration, keeping its existing serializers.CharField(read_only=True) field declaration unchanged.apps/api/plane/tests/contract/api/test_workspaces.py (2)
66-170: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test for the
DISABLE_WORKSPACE_CREATIONgate.The POST handler checks
DISABLE_WORKSPACE_CREATIONand returns 403, but no test covers this path. Adding a test ensures the gate is not accidentally bypassed in future refactors.✨ Suggested test: workspace creation disabled
`@pytest.mark.django_db` `@mock.patch`("plane.api.views.workspace.get_configuration_value") def test_create_workspace_disabled(self, mock_config, api_key_client): mock_config.return_value = ["1"] response = api_key_client.post(LIST_CREATE_URL, {"name": "Acme", "slug": "acme"}, format="json") assert response.status_code == status.HTTP_403_FORBIDDEN assert Workspace.objects.count() == 0🤖 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/contract/api/test_workspaces.py` around lines 66 - 170, Add a Django database test to TestWorkspaceListCreateAPIEndpoint covering the DISABLE_WORKSPACE_CREATION gate. Patch plane.api.views.workspace.get_configuration_value to return ["1"], submit a valid POST through api_key_client, and assert HTTP 403 with no Workspace created.
173-226: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a test for a non-owner admin updating a workspace.
The test suite covers a plain member (role 15) being denied PATCH, but there is no test for a non-owner admin (role 20). This gap directly relates to the
WorkspaceOwnerPermissionvs@allow_permission([ROLE.ADMIN])discrepancy flagged inviews/workspace.py. If the permission only allows the owner, a non-owner admin would be incorrectly blocked. Add a test to clarify the expected behavior.✨ Suggested test: non-owner admin update
`@pytest.mark.django_db` def test_non_owner_admin_can_update(self, api_key_client, admin_workspace): """A non-owner admin (role 20) should be able to update the workspace, mirroring the internal `@allow_permission`([ROLE.ADMIN]) behavior.""" response = api_key_client.patch( detail_url(admin_workspace.slug), {"name": "Admin Rename"}, format="json" ) assert response.status_code == status.HTTP_200_OK, f"Got {response.status_code}: {response.data!r}" admin_workspace.refresh_from_db() assert admin_workspace.name == "Admin Rename"🤖 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/contract/api/test_workspaces.py` around lines 173 - 226, Add a django_db test in TestWorkspaceDetailAPIEndpoint alongside test_member_can_retrieve_but_not_update that uses the admin_workspace fixture to PATCH the workspace as a non-owner admin. Assert a 200 OK response and verify after refresh_from_db that the workspace name changed to the submitted value, documenting that role 20 admins may update.
🤖 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/api/views/workspace.py`:
- Around line 206-208: Update the total_members calculation in the POST handler
to match the workspace_queryset annotation by filtering WorkspaceMember records
for the workspace with member__is_bot=False and is_active=True before counting.
Leave the response_data construction unchanged.
- Around line 193-221: Wrap the workspace creation and owner membership
insertion in WorkspaceViewSet.create using transaction.atomic(), including
serializer.save and WorkspaceMember.objects.create in the same atomic block.
Keep the response and background seeding after the transaction succeeds, and
ensure IntegrityError handling only maps the intended workspace slug conflict
rather than masking membership failures.
---
Nitpick comments:
In `@apps/api/plane/api/serializers/workspace.py`:
- Around line 43-45: Remove logo_url from the serializer’s Meta.read_only_fields
declaration, keeping its existing serializers.CharField(read_only=True) field
declaration unchanged.
In `@apps/api/plane/tests/contract/api/test_workspaces.py`:
- Around line 66-170: Add a Django database test to
TestWorkspaceListCreateAPIEndpoint covering the DISABLE_WORKSPACE_CREATION gate.
Patch plane.api.views.workspace.get_configuration_value to return ["1"], submit
a valid POST through api_key_client, and assert HTTP 403 with no Workspace
created.
- Around line 173-226: Add a django_db test in TestWorkspaceDetailAPIEndpoint
alongside test_member_can_retrieve_but_not_update that uses the admin_workspace
fixture to PATCH the workspace as a non-owner admin. Assert a 200 OK response
and verify after refresh_from_db that the workspace name changed to the
submitted value, documenting that role 20 admins may update.
🪄 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: 694cc143-ca8d-43a2-979a-640bb6584cb5
📒 Files selected for processing (7)
apps/api/plane/api/serializers/__init__.pyapps/api/plane/api/serializers/workspace.pyapps/api/plane/api/urls/__init__.pyapps/api/plane/api/urls/workspace.pyapps/api/plane/api/views/__init__.pyapps/api/plane/api/views/workspace.pyapps/api/plane/tests/contract/api/test_workspaces.py
Add a public (API-token) workspace surface so a workspace can be
provisioned headlessly, mirroring the internal app-API create logic
(plane/app/views/workspace/base.py::WorkSpaceViewSet).
- POST /api/v1/workspaces/ -> create; token user becomes owner + admin
(WorkspaceMember role 20); seeds default data via workspace_seed.delay.
Slug is optional and auto-generated from the name (slugify, 48-char
cap, numeric de-dup, reserved-slug aware) for headless provisioning;
an explicit slug is validated exactly like the internal API.
- GET /api/v1/workspaces/ -> list workspaces the user is a member of.
- GET /api/v1/workspaces/{slug}/ -> retrieve (members only, else 404).
- PATCH /api/v1/workspaces/{slug}/ -> partial update (admin only, matching
the internal @allow_permission([ROLE.ADMIN]) gate).
Validation mirrors WorkSpaceSerializer: name <=80 + has_alphanumeric +
no embedded URL; slug <=48 + ^[a-zA-Z0-9_-]+$ + RESTRICTED_WORKSPACE_SLUGS;
owner is read-only; DISABLE_WORKSPACE_CREATION honoured.
Routes are registered ahead of the invite/sticky DefaultRouters so the
workspaces/{slug}/ detail route wins over their API-root views. Additive
only; no model changes or migrations. Adds contract + unit tests.
Signed-off-by: Seena Fallah <[email protected]>
Description
Add a public (API-token) workspace surface so a workspace can be provisioned headlessly, mirroring the internal app-API create logic (plane/app/views/workspace/base.py::WorkSpaceViewSet).
the internal @allow_permission([ROLE.ADMIN]) gate).
Type of Change
Summary by CodeRabbit