feat: added work items count endpoint#48
Conversation
📝 WalkthroughWalkthroughAdds query DTOs and grouped (with optional subgrouping) count response models, plus a params serializer and WorkItems.count_workspace which GETs the workspace work-items count endpoint and returns the grouped-count response. ChangesWork Item Count Feature
Sequence Diagram(s)sequenceDiagram
participant Client
participant WorkItems
participant prepare_work_item_count_params
participant HTTP_API
participant Validator
Client->>WorkItems: count_workspace(workspace_slug, params)
WorkItems->>prepare_work_item_count_params: serialize params
prepare_work_item_count_params-->>WorkItems: query params dict
WorkItems->>HTTP_API: GET /workspaces/{slug}/work-items/count with params
HTTP_API-->>WorkItems: JSON response
WorkItems->>Validator: validate as WorkItemGroupedCountResponse
Validator-->>Client: WorkItemCountResponse (grouped)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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
This pull request adds a new Workspace-level Work Items “count” API to the SDK, enabling clients to retrieve either a single total count or grouped counts (by a chosen dimension), with optional filtering via structured filters or PQL.
Changes:
- Added
WorkItems.count_workspace(...)plus serialization helper for count-specific query params. - Introduced new query-param DTOs for the count endpoint:
WorkItemCountQueryParamsandWorkItemCountGroupBy. - Added new response models for flat and grouped count responses (
WorkItemFlatCountResponse,WorkItemGroupedCountResponse, etc.).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
plane/models/work_items.py |
Adds new Pydantic response models for flat vs. grouped count payloads. |
plane/models/query_params.py |
Adds count-endpoint query parameter DTOs and a Literal[...] type for supported group_by values. |
plane/api/work_items/base.py |
Implements count_workspace and a helper to serialize count query params (including JSON-encoding filters). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| from typing import TYPE_CHECKING, Any | ||
|
|
||
| from pydantic import BaseModel, ConfigDict, Field | ||
| from pydantic import BaseModel, ConfigDict, Field, model_validator |
| grouped_by: str | None = None | ||
| sub_grouped_by: str | None = None | ||
| total_count: int | None = None | ||
| grouped_counts: dict[str, WorkItemGroupCountEntry] | None = None |
| Without ``group_by`` the response is ``{"count": N}``. | ||
| With ``group_by`` the response is | ||
| ``{"grouped_by": ..., "total_count": N, "results": {group_key: {"count": N}}}``. | ||
| """ |
| "ORM field to group counts by. When supplied the response shape " | ||
| "changes from a flat ``{count}`` to a grouped " | ||
| "``{grouped_by, total_count, results}`` envelope." | ||
| ), |
| "Optional second field to group by, for nested grouping. Only valid if " | ||
| "`group_by` is also supplied. The response shape changes to include an " | ||
| "additional nesting level in the `results` envelope." | ||
| ), |
| sub_group_by: WorkItemCountGroupBy | None = Field( | ||
| None, | ||
| description=( | ||
| "Optional second field to group by, for nested grouping. Only valid if " | ||
| "`group_by` is also supplied. The response shape changes to include an " | ||
| "additional nesting level in the `results` envelope." | ||
| ), | ||
| ) | ||
|
|
| def count_workspace( | ||
| self, | ||
| workspace_slug: str, | ||
| params: WorkItemQueryParams | None = None, | ||
| ) -> PaginatedWorkItemResponse: | ||
| """List work items across the entire workspace. | ||
| params: WorkItemCountQueryParams | None = None, | ||
| ) -> WorkItemCountResponse: |
| "sub_grouped_by": null, | ||
| "total_count": 42, | ||
| "grouped_counts": {"urgent": {"count": 3}, "none": {"count": 6}} | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
plane/models/work_items.py (1)
607-624: ⚡ Quick winConsider adding validation for mutually exclusive shapes.
WorkItemGroupCountEntrysupports two distinct shapes (flat withcountvs nested withtotal_count+sub_grouped_counts), but all fields are optional with no validation enforcing the mutual exclusivity. This allows invalid states like:
- All fields
None- Both
countandtotal_countsetSince
model_validatorwas imported (line 3), consider adding a validator to enforce that exactly one shape is present:🛡️ Suggested validator
class WorkItemGroupCountEntry(BaseModel): """Count entry for a single group in a grouped count response. Shape depends on whether ``sub_group_by`` was supplied: * **Flat** (``group_by`` only): ``{"count": N}`` * **Nested** (``group_by`` + ``sub_group_by``): ``{"total_count": N, "sub_grouped_counts": {sub_key: {"count": N}}}`` """ model_config = ConfigDict(extra="allow", populate_by_name=True) # flat grouped shape (group_by only) count: int | None = None # sub-grouped shape (group_by + sub_group_by) total_count: int | None = None sub_grouped_counts: dict[str, WorkItemSubGroupCountEntry] | None = None + + `@model_validator`(mode="after") + def validate_shape(self) -> "WorkItemGroupCountEntry": + """Ensure exactly one of flat or nested shape is present.""" + has_flat = self.count is not None + has_nested = self.total_count is not None or self.sub_grouped_counts is not None + + if not has_flat and not has_nested: + raise ValueError("Count entry must have either 'count' (flat) or 'total_count'/'sub_grouped_counts' (nested)") + if has_flat and has_nested: + raise ValueError("Count entry cannot mix flat ('count') and nested ('total_count'/'sub_grouped_counts') shapes") + if has_nested and (self.total_count is None or self.sub_grouped_counts is None): + raise ValueError("Nested shape requires both 'total_count' and 'sub_grouped_counts'") + + return self🤖 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/models/work_items.py` around lines 607 - 624, WorkItemGroupCountEntry currently allows invalid combinations because count, total_count, and sub_grouped_counts are all optional; add a model-level validator on WorkItemGroupCountEntry (use model_validator with mode="after") that enforces exactly one valid shape: either a flat shape where count is not None and both total_count and sub_grouped_counts are None, or a nested shape where total_count is not None and sub_grouped_counts is a dict (and count is None); raise ValueError with a clear message for all-none or both-shapes-present cases so invalid states (all None or both count and total_count set) are rejected.
🤖 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.
Nitpick comments:
In `@plane/models/work_items.py`:
- Around line 607-624: WorkItemGroupCountEntry currently allows invalid
combinations because count, total_count, and sub_grouped_counts are all
optional; add a model-level validator on WorkItemGroupCountEntry (use
model_validator with mode="after") that enforces exactly one valid shape: either
a flat shape where count is not None and both total_count and sub_grouped_counts
are None, or a nested shape where total_count is not None and sub_grouped_counts
is a dict (and count is None); raise ValueError with a clear message for
all-none or both-shapes-present cases so invalid states (all None or both count
and total_count set) are rejected.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0d2b5a0b-6d86-46e0-8d85-f5b752940100
📒 Files selected for processing (3)
plane/api/work_items/base.pyplane/models/query_params.pyplane/models/work_items.py
🚧 Files skipped from review as they are similar to previous changes (1)
- plane/models/query_params.py
This pull request introduces a new API for counting work items in a workspace, with support for filtering and grouping, and adds corresponding data models and query parameter types. The changes include a new
count_workspacemethod, new query parameter and response models, and supporting serialization logic.New Work Item Counting API
WorkItems.count_workspace: Adds a new method to return the count of work items in a workspace, supporting optional filters, PQL, and grouping by various fields. The method returns either a flat count or grouped counts depending on the parameters.prepare_work_item_count_params: Adds a helper function to serialize work item count query parameters, including filters, for HTTP requests.Models and Query Parameter Types
WorkItemCountQueryParamsandWorkItemCountGroupBy: Introduces new query parameter DTOs for the count endpoint, supporting filters, PQL, and agroup_byfield for grouped counts.WorkItemFlatCountResponse,WorkItemGroupedCountResponse,WorkItemGroupCountEntry, andWorkItemCountResponse: Adds new response models to represent flat and grouped count results from the count endpoint.Imports and Type Annotations
plane/api/work_items/base.pyto include the new query parameter and response models.Summary by CodeRabbit