Skip to content

feat: rich filters + pql on work-item list endpoints, add list_workspace [WEB-7393][PAI-1461]#37

Open
dheeru0198 wants to merge 3 commits into
mainfrom
feat/web-7393-rich-filter-pql-workspace-workitems
Open

feat: rich filters + pql on work-item list endpoints, add list_workspace [WEB-7393][PAI-1461]#37
dheeru0198 wants to merge 3 commits into
mainfrom
feat/web-7393-rich-filter-pql-workspace-workitems

Conversation

@dheeru0198
Copy link
Copy Markdown
Member

@dheeru0198 dheeru0198 commented May 26, 2026

Summary

Exposes the new external API's structured filtering capability across every work-item list method in the SDK, plus a new list_workspace method for the workspace-scoped paginated list endpoint added in makeplane/plane-ee#7376.

Changes

  • WorkItemQueryParams now accepts filters: dict[str, Any] | None. The existing pql: str | None field is unchanged. A small _prepare_work_item_params helper JSON-encodes filters into the filters= query string the API expects; everything else passes through as-is.
  • New WorkItems.list_workspace(workspace_slug, params) calling GET /workspaces/<slug>/work-items/ — paginated envelope with full total_results. Spans every project in the workspace the caller can view (per-project authorization and conditional grants are honored server-side).
  • WorkItems.list and WorkItems.list_archived now route through _prepare_work_item_params, so filters and pql work uniformly.
  • Cycles.list_work_items and Modules.list_work_items accept WorkItemQueryParams (typed path) with the same serialization, and still accept a plain Mapping[str, Any] for backwards compatibility.
  • Real-API unit tests for filters on list, plus list_workspace with and without filters.
  • README: new "Filtering work items" subsection with examples.
  • Version bump 0.2.12 → 0.2.13.

Backend

  • makeplane/plane-ee#7376 (feat/web-7393-external-workitem-rich-filter-pql) — this PR depends on those endpoints being deployed.

Test plan

  • ruff check — clean on touched files
  • black --check — clean (auto-formatted)
  • Import smoke test — _prepare_work_item_params(WorkItemQueryParams(filters={...}, pql='...', per_page=5)) returns the expected serialized dict
  • Real-API run: set PLANE_BASE_URL, PLANE_API_KEY, WORKSPACE_SLUG against an env with the plane-ee branch deployed; pytest tests/unit/test_work_items.py -k "filter or workspace" should pass

References

  • Work item: WEB-7393

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added structured filtering support for work item queries alongside existing PQL capabilities. Filtering now available when listing work items, cycles, and modules.
  • Documentation

    • Expanded query parameters documentation with detailed filtering examples and usage patterns across different list endpoints.
  • Tests

    • Added tests for filtering functionality across work item listing operations.

Review Change Stack

…orkspace`

Expose the new external API's structured filtering capability on every
work-item list method in the SDK.

- WorkItemQueryParams now accepts `filters: dict[str, Any] | None`
  (the existing `pql: str | None` field is unchanged). A small
  `_prepare_work_item_params` helper JSON-encodes `filters` into the
  `filters=` query string the API expects.
- New `WorkItems.list_workspace(workspace_slug, params)` calling
  `GET /workspaces/<slug>/work-items/` — paginated, total_results,
  spans every project the caller can view.
- `WorkItems.list` and `WorkItems.list_archived` now route through
  `_prepare_work_item_params`, so `filters` and `pql` work uniformly.
- `Cycles.list_work_items` and `Modules.list_work_items` accept
  `WorkItemQueryParams` (typed path) with the same serialization, and
  still accept a plain `Mapping[str, Any]` for backwards compatibility.
- Real-API unit tests for `filters` on `list`, plus `list_workspace`
  with and without `filters`.
- Bump version to 0.2.13.

Backend: makeplane/plane-ee#7376

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

📝 Walkthrough

Walkthrough

This PR extends the Plane Python SDK with structured query filtering for work items. It introduces a filters field to WorkItemQueryParams, implements a helper to serialize filters as compact JSON, and integrates this typed parameter support across work-item, cycles, and modules listing endpoints with new tests and documentation.

Changes

Work Item Filtering Support

Layer / File(s) Summary
Query Parameters Model
plane/models/query_params.py
WorkItemQueryParams adds filters: dict[str, Any] | None alongside the updated pql field, with expanded documentation for both structured and string-based filtering.
Work Items Base Implementation
plane/api/work_items/base.py
New _prepare_work_item_params helper JSON-encodes structured filters into query parameters. WorkItems.list and list_archived updated to use the helper with revised docstrings demonstrating both pql and filters usage.
Cycles Integration
plane/api/cycles.py
Cycles.list_work_items accepts WorkItemQueryParams and prepares it via the base helper before querying cycle-issues. Method signature widened to accept both typed params and raw mappings with documentation updates.
Modules Integration
plane/api/modules.py
Modules.list_work_items accepts WorkItemQueryParams and conditionally serializes it via the base helper before querying module-issues, with backwards-compatible mapping passthrough and docstring clarifications.
Filtering Tests
tests/unit/test_work_items.py
Three new test methods: test_list_work_items_with_filters (verifies structured filter matching), test_list_workspace_work_items (verifies paginated envelope shape), and test_list_workspace_work_items_with_filters (validates filter result reduction).
Documentation & Version
README.md, pyproject.toml
README expanded with filtering subsection documenting both filters and pql with usage examples across work-items, cycles, and modules. Version bumped from 0.2.12 to 0.2.13.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • makeplane/plane-python-sdk#36: Both PRs modify work-item listing/archive behavior in plane/api/work_items/base.py—the retrieved PR adds WorkItems.list_archived, and the main PR changes list_archived to use the new _prepare_work_item_params (including structured filters/pql serialization).

Suggested reviewers

  • Prashant-Surya

Poem

🐰 Filters dance and PQL flows free,
Structured queries now for you and me!
Cycles and modules aligned so bright,
JSON-encoded, formatted just right. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title accurately captures the main changes: addition of rich filters and PQL support on work-item list endpoints, plus the new list_workspace functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-7393-rich-filter-pql-workspace-workitems

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 and usage tips.

@makeplane
Copy link
Copy Markdown

makeplane Bot commented May 26, 2026

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 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 `@plane/api/cycles.py`:
- Around line 162-163: Replace the non-compliant endpoint segments and add
trailing slashes: change any occurrences of "cycle-issues" in the URL
construction to "work-items" and ensure the URLs follow the pattern
"{base_path}/api/v1{resource_base_path}/{endpoint}/" with a trailing "/" (e.g.
update the string
f"{workspace_slug}/projects/{project_id}/cycles/{cycle_id}/cycle-issues" to use
"work-items" and append "/" ), and apply the same fix to the other occurrences
referenced (the similar URL constructions at the other reported locations) so
all endpoint strings end with a trailing slash and use "work-items" instead of
"issue"/"cycle-issues".
- Around line 142-160: Update Cycles.list_work_items to accept only the DTO type
by changing the params handling to require WorkItemQueryParams | None (remove
the Mapping branch) and call
_prepare_work_item_params(params.model_dump(exclude_none=True)) when params is
provided; ensure the outgoing request uses that serialized mapping and validate
the response with PaginatedCycleWorkItemResponse.model_validate(...) before
returning. Target the Cycles.list_work_items function and use
WorkItemQueryParams, _prepare_work_item_params, and
PaginatedCycleWorkItemResponse.model_validate to locate and implement the
change.

In `@plane/api/modules.py`:
- Around line 161-162: The endpoint string using "module-issues" should be
renamed to use "module-work-items" and must include a trailing slash to follow
API conventions; update the f-string at the call that builds the modules
endpoint (the line containing
f"{workspace_slug}/projects/{project_id}/modules/{module_id}/module-issues") to
f"{workspace_slug}/projects/{project_id}/modules/{module_id}/module-work-items/"
ensuring the URL matches the required
{base_path}/api/v1{resource_base_path}/{endpoint}/ pattern and preserves
existing query_params handling.
- Around line 141-159: The list_work_items method currently accepts a raw
Mapping which bypasses DTO validation; change the signature to accept only
WorkItemQueryParams (remove Mapping[str, Any] | None), and when building
query_params use params.model_dump(exclude_none=True) (or call
_prepare_work_item_params(params) if that helper returns a dict from the DTO),
removing the raw-mapping branch; also ensure the method validates responses
using PaginatedModuleWorkItemResponse.model_validate(...) before returning.

In `@plane/api/work_items/base.py`:
- Around line 199-201: Several endpoint f-strings used in calls to
self._get/_post are missing the terminal slash (e.g.
f"{workspace_slug}/projects/{project_id}/work-items" in the response =
self._get(...) call); update these formatted paths to include a trailing '/' so
they follow the API convention. Locate calls referencing workspace_slug,
project_id, work_item_id (and uses of _prepare_work_item_params) and append '/'
to each endpoint string passed into self._get and similar HTTP helpers (also
adjust the other occurrences in the same module referenced near the other
f-strings) to ensure every "{...}/..." path ends with '/'.

In `@tests/unit/test_work_items.py`:
- Around line 140-147: The current test only checks counts and presence which
can pass if filtering is ignored; update the assertions in the test (referencing
filtered, unfiltered, created.id, filtered.results, filtered.total_results) to
explicitly verify filter semantics by asserting that every item in
filtered.results satisfies the urgent filter predicate (e.g., item.priority ==
"urgent" or item.is_urgent — use the actual field on the WorkItem model) and
keep the pagination tolerance separate: still allow created.id to be absent from
this page if filtered.total_results > 0, but do not use that condition to bypass
the requirement that all returned items match the filter.
🪄 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: dc8a21e8-6990-49e6-b9ad-737b19edcc49

📥 Commits

Reviewing files that changed from the base of the PR and between 88ade49 and d55f45e.

📒 Files selected for processing (7)
  • README.md
  • plane/api/cycles.py
  • plane/api/modules.py
  • plane/api/work_items/base.py
  • plane/models/query_params.py
  • pyproject.toml
  • tests/unit/test_work_items.py

Comment thread plane/api/cycles.py Outdated
Comment on lines +142 to +160
params: WorkItemQueryParams | Mapping[str, Any] | None = None,
) -> PaginatedCycleWorkItemResponse:
"""List work items in a cycle.

Supports the same ``filters`` and ``pql`` query parameters as
:meth:`WorkItems.list`.

Args:
workspace_slug: The workspace slug identifier
project_id: UUID of the project
cycle_id: UUID of the cycle
params: Optional query parameters
params: Optional query parameters. Prefer ``WorkItemQueryParams``;
a plain mapping is also accepted for backwards compatibility
and is passed through unchanged.
"""
if isinstance(params, WorkItemQueryParams):
query_params: Mapping[str, Any] | None = _prepare_work_item_params(params)
else:
query_params = params
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 | 🟠 Major | 🏗️ Heavy lift

Enforce DTO-only params for resource method inputs

Cycles.list_work_items still accepts plain mappings (Line 142, Line 159-Line 160). This conflicts with the resource contract and weakens typed validation for filters/pql input.

Suggested direction
-    def list_work_items(
+    def list_work_items(
         self,
         workspace_slug: str,
         project_id: str,
         cycle_id: str,
-        params: WorkItemQueryParams | Mapping[str, Any] | None = None,
+        params: WorkItemQueryParams | None = None,
     ) -> PaginatedCycleWorkItemResponse:
@@
-        if isinstance(params, WorkItemQueryParams):
-            query_params: Mapping[str, Any] | None = _prepare_work_item_params(params)
-        else:
-            query_params = params
+        query_params: Mapping[str, Any] | None = (
+            _prepare_work_item_params(params) if params is not None else None
+        )

As per coding guidelines, "Resource methods must accept Pydantic DTOs, serialize with model_dump(exclude_none=True), and validate responses with Model.model_validate()."

🤖 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 `@plane/api/cycles.py` around lines 142 - 160, Update Cycles.list_work_items to
accept only the DTO type by changing the params handling to require
WorkItemQueryParams | None (remove the Mapping branch) and call
_prepare_work_item_params(params.model_dump(exclude_none=True)) when params is
provided; ensure the outgoing request uses that serialized mapping and validate
the response with PaginatedCycleWorkItemResponse.model_validate(...) before
returning. Target the Cycles.list_work_items function and use
WorkItemQueryParams, _prepare_work_item_params, and
PaginatedCycleWorkItemResponse.model_validate to locate and implement the
change.

Comment thread plane/api/cycles.py Outdated
Comment on lines +162 to +163
f"{workspace_slug}/projects/{project_id}/cycles/{cycle_id}/cycle-issues",
params=query_params,
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 | 🟠 Major | ⚡ Quick win

Endpoint path naming/convention is out of policy

Line 162 uses cycle-issues, and Lines 162, 195, and 206 omit trailing /. This violates endpoint naming and URL-format rules for API resources.

Suggested patch
-            f"{workspace_slug}/projects/{project_id}/cycles/{cycle_id}/cycle-issues",
+            f"{workspace_slug}/projects/{project_id}/cycles/{cycle_id}/cycle-work-items/",
@@
-        self._post(f"{workspace_slug}/projects/{project_id}/cycles/{cycle_id}/archive", {})
+        self._post(f"{workspace_slug}/projects/{project_id}/cycles/{cycle_id}/archive/", {})
@@
-        self._delete(f"{workspace_slug}/projects/{project_id}/archived-cycles/{cycle_id}/unarchive")
+        self._delete(
+            f"{workspace_slug}/projects/{project_id}/archived-cycles/{cycle_id}/unarchive/"
+        )

As per coding guidelines, "Never use 'Issue' in endpoint or parameter names — always use 'Work Item' instead" and "All API endpoints should end with a trailing / and follow URL convention: {base_path}/api/v1{resource_base_path}/{endpoint}/."

Also applies to: 195-195, 206-206

🤖 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 `@plane/api/cycles.py` around lines 162 - 163, Replace the non-compliant
endpoint segments and add trailing slashes: change any occurrences of
"cycle-issues" in the URL construction to "work-items" and ensure the URLs
follow the pattern "{base_path}/api/v1{resource_base_path}/{endpoint}/" with a
trailing "/" (e.g. update the string
f"{workspace_slug}/projects/{project_id}/cycles/{cycle_id}/cycle-issues" to use
"work-items" and append "/" ), and apply the same fix to the other occurrences
referenced (the similar URL constructions at the other reported locations) so
all endpoint strings end with a trailing slash and use "work-items" instead of
"issue"/"cycle-issues".

Comment thread plane/api/modules.py Outdated
Comment on lines +141 to +159
params: WorkItemQueryParams | Mapping[str, Any] | None = None,
) -> PaginatedModuleWorkItemResponse:
"""List work items in a module.

Supports the same ``filters`` and ``pql`` query parameters as
:meth:`WorkItems.list`.

Args:
workspace_slug: The workspace slug identifier
project_id: UUID of the project
module_id: UUID of the module
params: Optional query parameters
params: Optional query parameters. Prefer ``WorkItemQueryParams``;
a plain mapping is also accepted for backwards compatibility
and is passed through unchanged.
"""
if isinstance(params, WorkItemQueryParams):
query_params: Mapping[str, Any] | None = _prepare_work_item_params(params)
else:
query_params = params
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 | 🟠 Major | 🏗️ Heavy lift

Keep list_work_items strictly typed to query DTO

Line 141 and Line 159 permit raw mappings, bypassing DTO constraints and creating a second unvalidated input path.

Suggested direction
-    def list_work_items(
+    def list_work_items(
         self,
         workspace_slug: str,
         project_id: str,
         module_id: str,
-        params: WorkItemQueryParams | Mapping[str, Any] | None = None,
+        params: WorkItemQueryParams | None = None,
     ) -> PaginatedModuleWorkItemResponse:
@@
-        if isinstance(params, WorkItemQueryParams):
-            query_params: Mapping[str, Any] | None = _prepare_work_item_params(params)
-        else:
-            query_params = params
+        query_params: Mapping[str, Any] | None = (
+            _prepare_work_item_params(params) if params is not None else None
+        )

As per coding guidelines, "Resource methods must accept Pydantic DTOs, serialize with model_dump(exclude_none=True), and validate responses with Model.model_validate()."

📝 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
params: WorkItemQueryParams | Mapping[str, Any] | None = None,
) -> PaginatedModuleWorkItemResponse:
"""List work items in a module.
Supports the same ``filters`` and ``pql`` query parameters as
:meth:`WorkItems.list`.
Args:
workspace_slug: The workspace slug identifier
project_id: UUID of the project
module_id: UUID of the module
params: Optional query parameters
params: Optional query parameters. Prefer ``WorkItemQueryParams``;
a plain mapping is also accepted for backwards compatibility
and is passed through unchanged.
"""
if isinstance(params, WorkItemQueryParams):
query_params: Mapping[str, Any] | None = _prepare_work_item_params(params)
else:
query_params = params
params: WorkItemQueryParams | None = None,
) -> PaginatedModuleWorkItemResponse:
"""List work items in a module.
Supports the same ``filters`` and ``pql`` query parameters as
:meth:`WorkItems.list`.
Args:
workspace_slug: The workspace slug identifier
project_id: UUID of the project
module_id: UUID of the module
params: Optional query parameters. Prefer ``WorkItemQueryParams``;
a plain mapping is also accepted for backwards compatibility
and is passed through unchanged.
"""
query_params: Mapping[str, Any] | None = (
_prepare_work_item_params(params) if params is not None else 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 `@plane/api/modules.py` around lines 141 - 159, The list_work_items method
currently accepts a raw Mapping which bypasses DTO validation; change the
signature to accept only WorkItemQueryParams (remove Mapping[str, Any] | None),
and when building query_params use params.model_dump(exclude_none=True) (or call
_prepare_work_item_params(params) if that helper returns a dict from the DTO),
removing the raw-mapping branch; also ensure the method validates responses
using PaginatedModuleWorkItemResponse.model_validate(...) before returning.

Comment thread plane/api/modules.py Outdated
Comment on lines +161 to +162
f"{workspace_slug}/projects/{project_id}/modules/{module_id}/module-issues",
params=params,
params=query_params,
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 | 🟠 Major | ⚡ Quick win

module-issues path and missing trailing slash violate API conventions

Line 161 uses module-issues and does not end the endpoint with /.

Suggested patch
-            f"{workspace_slug}/projects/{project_id}/modules/{module_id}/module-issues",
+            f"{workspace_slug}/projects/{project_id}/modules/{module_id}/module-work-items/",

As per coding guidelines, "Never use 'Issue' in endpoint or parameter names — always use 'Work Item' instead" and "All API endpoints should end with a trailing / and follow URL convention: {base_path}/api/v1{resource_base_path}/{endpoint}/."

📝 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
f"{workspace_slug}/projects/{project_id}/modules/{module_id}/module-issues",
params=params,
params=query_params,
f"{workspace_slug}/projects/{project_id}/modules/{module_id}/module-work-items/",
params=query_params,
🤖 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 `@plane/api/modules.py` around lines 161 - 162, The endpoint string using
"module-issues" should be renamed to use "module-work-items" and must include a
trailing slash to follow API conventions; update the f-string at the call that
builds the modules endpoint (the line containing
f"{workspace_slug}/projects/{project_id}/modules/{module_id}/module-issues") to
f"{workspace_slug}/projects/{project_id}/modules/{module_id}/module-work-items/"
ensuring the URL matches the required
{base_path}/api/v1{resource_base_path}/{endpoint}/ pattern and preserves
existing query_params handling.

Comment thread plane/api/work_items/base.py Outdated
Comment on lines +199 to +201
response = self._get(
f"{workspace_slug}/projects/{project_id}/work-items",
params=_prepare_work_item_params(params),
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 | 🟠 Major | ⚡ Quick win

Add trailing slashes to touched endpoint paths.

These changed URLs still omit the terminal /, which breaks the API path convention and can cause routing/redirect inconsistencies.

Suggested patch
-            f"{workspace_slug}/projects/{project_id}/work-items",
+            f"{workspace_slug}/projects/{project_id}/work-items/",
             params=_prepare_work_item_params(params),
@@
-            f"{workspace_slug}/work-items",
+            f"{workspace_slug}/work-items/",
             params=_prepare_work_item_params(params),
@@
-            f"{workspace_slug}/projects/{project_id}/archived-work-items",
+            f"{workspace_slug}/projects/{project_id}/archived-work-items/",
             params=_prepare_work_item_params(params),
@@
-        self._delete(f"{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/unarchive")
+        self._delete(f"{workspace_slug}/projects/{project_id}/work-items/{work_item_id}/unarchive/")

As per coding guidelines, "All API endpoints should end with a trailing / and follow URL convention: {base_path}/api/v1{resource_base_path}/{endpoint}/".

Also applies to: 234-236, 319-320, 352-352

🤖 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 `@plane/api/work_items/base.py` around lines 199 - 201, Several endpoint
f-strings used in calls to self._get/_post are missing the terminal slash (e.g.
f"{workspace_slug}/projects/{project_id}/work-items" in the response =
self._get(...) call); update these formatted paths to include a trailing '/' so
they follow the API convention. Locate calls referencing workspace_slug,
project_id, work_item_id (and uses of _prepare_work_item_params) and append '/'
to each endpoint string passed into self._get and similar HTTP helpers (also
adjust the other occurrences in the same module referenced near the other
f-strings) to ensure every "{...}/..." path ends with '/'.

Comment thread tests/unit/test_work_items.py Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for the Plane external API’s “rich” work-item filtering by introducing structured filters alongside existing pql across work-item list endpoints, and exposes a new workspace-scoped work-item listing method in the SDK.

Changes:

  • Add filters: dict[str, Any] | None to WorkItemQueryParams and serialize it as a JSON string in the filters= query parameter.
  • Add WorkItems.list_workspace(...) for workspace-wide work-item listing (paginated, includes total_results).
  • Extend cycles/modules list_work_items to accept WorkItemQueryParams while keeping Mapping support, plus update tests/docs and bump version to 0.2.13.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
plane/models/query_params.py Adds filters and expands pql documentation on WorkItemQueryParams.
plane/api/work_items/base.py Implements _prepare_work_item_params (JSON-encodes filters) and adds list_workspace; routes list methods through the helper.
plane/api/cycles.py Accepts WorkItemQueryParams for cycle work-item listing and applies the shared param-prep path.
plane/api/modules.py Accepts WorkItemQueryParams for module work-item listing and applies the shared param-prep path.
tests/unit/test_work_items.py Adds real-API tests for structured filters and for list_workspace (with/without filters).
README.md Documents structured filters vs PQL and adds usage examples including workspace-scoped listing.
pyproject.toml Bumps package version to 0.2.13.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread plane/api/modules.py Outdated
Comment on lines 156 to 160
if isinstance(params, WorkItemQueryParams):
query_params: Mapping[str, Any] | None = _prepare_work_item_params(params)
else:
query_params = params
response = self._get(
Comment thread plane/api/modules.py Outdated
Comment on lines 14 to 16
from .work_items.base import _prepare_work_item_params


Comment thread plane/api/cycles.py Outdated
Comment on lines 157 to 161
if isinstance(params, WorkItemQueryParams):
query_params: Mapping[str, Any] | None = _prepare_work_item_params(params)
else:
query_params = params
response = self._get(
Comment thread plane/api/cycles.py Outdated
Comment on lines 15 to 17
from .work_items.base import _prepare_work_item_params


Comment thread tests/unit/test_work_items.py Outdated
Comment on lines +141 to +145
assert created.id in [item.id for item in filtered.results] or (
# If the workspace has many urgent items, the created one may be
# on a later page — accept either presence on this page or that
# at least one urgent item is returned overall.
filtered.total_results
Comment thread README.md Outdated
@@ -748,9 +748,55 @@ The SDK provides comprehensive Pydantic v2 models for all API operations.

- `BaseQueryParams` - Base query parameters
- `PaginatedQueryParams` - Pagination support (per_page, page)
Drop the `or filtered.total_results > 0` fallback (which could pass even
when filtering was a no-op) and assert directly that every returned item
has `priority == "urgent"` — that's the property the test is meant to
prove.

Addresses CodeRabbit review.
Copilot caught a real bug: `Cycles.list_work_items` / `Modules.list_work_items`
accept a plain `Mapping[str, Any]` for BC, but when callers passed `filters`
as a dict through that path the SDK forwarded it to `requests` unchanged.
The API expects `filters` as a JSON-encoded string, so the request would
produce an invalid query parameter.

- Extend `prepare_work_item_params` (renamed from `_prepare_work_item_params`
  since it's now used across resources, no longer private to the work_items
  module) to accept either a `WorkItemQueryParams` DTO or a plain mapping,
  and normalise `filters` to a JSON string in both paths.
- Simplify `Cycles.list_work_items` and `Modules.list_work_items` to use
  the unified helper — drops the `isinstance` branch.
- Fix README: `PaginatedQueryParams` is cursor-based (cursor, per_page),
  not (per_page, page) — that combination has never existed in the model.

Addresses Copilot review on PR #37.
@sunder-ch sunder-ch changed the title feat: rich filters + pql on work-item list endpoints, add list_workspace [WEB-7393] feat: rich filters + pql on work-item list endpoints, add list_workspace [WEB-7393][PAI-1461] May 26, 2026
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