Skip to content

feat: add filter fields to WorkItemQueryParams#24

Open
lifeiscontent wants to merge 1 commit intomakeplane:mainfrom
lifeiscontent:feat/work-item-query-filters
Open

feat: add filter fields to WorkItemQueryParams#24
lifeiscontent wants to merge 1 commit intomakeplane:mainfrom
lifeiscontent:feat/work-item-query-filters

Conversation

@lifeiscontent
Copy link
Contributor

@lifeiscontent lifeiscontent commented Mar 23, 2026

Summary

Adds server-side filtering support to WorkItemQueryParams, enabling callers to filter work items by assignee, state, priority, labels, and more.

Changes

  • Added 8 new fields to WorkItemQueryParams:
    • assignee_id__in — filter by assignee UUIDs
    • state_id__in — filter by state UUIDs
    • state_group__in — filter by state group (backlog, unstarted, started, completed, cancelled)
    • priority__in — filter by priority (urgent, high, medium, low, none)
    • label_id__in — filter by label UUIDs
    • created_by_id__in — filter by creator UUIDs
    • is_draft — filter by draft status
    • is_archived — filter by archived status
  • Added a model_serializer to convert list fields to comma-separated strings for compatibility with django-filters BaseInFilter.

Context

The Plane REST API (via IssueFilterSet in django-filters) supports all of these query parameters, but WorkItemQueryParams had no filter fields — only pagination/ordering. This meant SDK users (including the MCP server) could not filter work items server-side and had to fetch all items and filter client-side.

Companion PR

  • makeplane/plane-mcp-server — exposes these filters as MCP tool parameters

Summary by CodeRabbit

  • New Features
    • Enhanced work item filtering with new options to filter by assignees, states, state groups, priorities, labels, and creators.
    • Added draft and archived status filtering for work items.

Add assignee_id__in, state_id__in, state_group__in, priority__in,
label_id__in, created_by_id__in, is_draft, and is_archived fields
to WorkItemQueryParams to support server-side filtering of work items.

Add model_serializer to convert list fields to comma-separated strings
for compatibility with django-filters BaseInFilter.
@coderabbitai
Copy link

coderabbitai bot commented Mar 23, 2026

📝 Walkthrough

Walkthrough

Added eight new optional filter fields to WorkItemQueryParams for filtering by assignee, state, state group, priority, label, and creator, along with draft and archived status flags. A custom Pydantic serializer converts list field values into comma-separated strings for django-filter compatibility.

Changes

Cohort / File(s) Summary
Query Parameter Filtering
plane/models/query_params.py
Added __in bulk filter fields (assignee_id__in, state_id__in, state_group__in, priority__in, label_id__in, created_by_id__in) and boolean status filters (is_draft, is_archived). Implemented a Pydantic model_serializer to convert list-typed field values into comma-separated strings for query parameter serialization.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 With filter fields we hop and bound,
Through assignees and states profound,
Lists to strings, comma-separated dreams,
Querying work in clever schemes! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding filter fields to WorkItemQueryParams for server-side filtering support.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

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

Tip

You can validate your CodeRabbit configuration file in your editor.

If your editor has YAML language server, you can enable auto-completion and validation by adding # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json at the top of your CodeRabbit configuration file.

Copy link

@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.

🧹 Nitpick comments (1)
plane/models/query_params.py (1)

98-105: Implementation is correct; optional improvement for empty list handling.

The wrap serializer correctly post-processes the default serialization to convert lists to comma-separated strings for django-filters compatibility. The handler(self) signature is valid in Pydantic v2.

Minor consideration: an empty list [] will serialize to "", producing a query parameter like ?assignee_id__in=. If this is undesired, filter out empty strings:

Optional: exclude empty list results
 `@model_serializer`(mode="wrap")
 def _serialize(self, handler):  # type: ignore[no-untyped-def]
     """Serialize list fields as comma-separated strings for django-filters."""
     data = handler(self)
     for key, value in data.items():
         if isinstance(value, list):
             data[key] = ",".join(str(v) for v in value)
+    return {k: v for k, v in data.items() if v != ""}
-    return data
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plane/models/query_params.py` around lines 98 - 105, The current _serialize
method converts list fields to comma-separated strings but leaves empty lists as
an empty string (e.g., ?assignee_id__in=); update _serialize (the
model_serializer wrapper that calls handler(self)) to skip or remove keys whose
value is an empty list so they are not serialized as empty strings—detect list
values in the data dict after handler(self) and if the list is empty, delete
data[key] (or set it to None/omit it) instead of converting to ",".join, while
still converting non-empty lists as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@plane/models/query_params.py`:
- Around line 98-105: The current _serialize method converts list fields to
comma-separated strings but leaves empty lists as an empty string (e.g.,
?assignee_id__in=); update _serialize (the model_serializer wrapper that calls
handler(self)) to skip or remove keys whose value is an empty list so they are
not serialized as empty strings—detect list values in the data dict after
handler(self) and if the list is empty, delete data[key] (or set it to None/omit
it) instead of converting to ",".join, while still converting non-empty lists as
before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53d91f60-ecbd-42b1-a54d-e0c3e241f99c

📥 Commits

Reviewing files that changed from the base of the PR and between 5edb2fc and 94cf652.

📒 Files selected for processing (1)
  • plane/models/query_params.py

Copy link

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 first-class server-side filtering to the SDK’s WorkItemQueryParams, aligning the Python client with Plane’s REST API filtering capabilities and avoiding client-side filtering workarounds.

Changes:

  • Added filter fields to WorkItemQueryParams (assignee/state/priority/labels/creator + draft/archived flags).
  • Added a Pydantic model_serializer to serialize list filters as comma-separated strings for django-filters BaseInFilter compatibility.

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

Comment on lines +98 to +100
@model_serializer(mode="wrap")
def _serialize(self, handler): # type: ignore[no-untyped-def]
"""Serialize list fields as comma-separated strings for django-filters."""
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The @model_serializer is currently untyped and relies on # type: ignore[no-untyped-def], which conflicts with the repo’s strict mypy config. Please type the serializer signature (e.g., using Pydantic’s SerializerFunctionWrapHandler and optional SerializationInfo) so we can remove the type ignore and keep type safety.

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +80
state_group__in: list[str] | None = Field(
None,
description="Filter by state groups (backlog, unstarted, started, completed, cancelled)",
)
priority__in: list[str] | None = Field(
None,
description="Filter by priority levels (urgent, high, medium, low, none)",
)
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

state_group__in and priority__in are typed as list[str], but the codebase already defines constrained Literal types (GroupEnum, PriorityEnum) used across models. Consider typing these as list[GroupEnum] | None and list[PriorityEnum] | None to prevent invalid values at compile/validation time and keep consistency with the rest of the SDK.

Copilot uses AI. Check for mistakes.
"""Serialize list fields as comma-separated strings for django-filters."""
data = handler(self)
for key, value in data.items():
if isinstance(value, list):
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

The model-level serializer converts any list-valued field into a comma-separated string. That’s fine for the current __in filters, but it will also affect any future list fields added to WorkItemQueryParams (even if the API expects repeated query params instead of CSV). Consider limiting this conversion to the known *__in keys (or an explicit allowlist) to avoid surprising serialization changes later.

Suggested change
if isinstance(value, list):
if key.endswith("__in") and isinstance(value, list):

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +105
@model_serializer(mode="wrap")
def _serialize(self, handler): # type: ignore[no-untyped-def]
"""Serialize list fields as comma-separated strings for django-filters."""
data = handler(self)
for key, value in data.items():
if isinstance(value, list):
data[key] = ",".join(str(v) for v in value)
return data
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

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

New serialization behavior (converting list filters to comma-separated strings) isn’t covered by tests. Please add a unit test that asserts WorkItemQueryParams(assignee_id__in=[...]).model_dump(exclude_none=True) produces a comma-separated string, since this behavior is critical for server-side filtering to work with requests query encoding.

Copilot uses AI. Check for mistakes.
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