Skip to content

Commit 384aab9

Browse files
committed
fix: resolve milestone listing GraphQL filter issues
- Replace server-side filtering with client-side filtering for project milestones - Add proper data structure handling in milestone listing command - Fix GraphQL schema incompatibility with ProjectMilestoneFilter This resolves issues where milestone listing by project would fail due to Linear's GraphQL API not supporting direct project filtering for milestones. The fix ensures reliable milestone listing by fetching all milestones and filtering them client-side based on project ID.
1 parent ec5cf32 commit 384aab9

File tree

1 file changed

+27
-15
lines changed

1 file changed

+27
-15
lines changed

src/linear_cli/cli/commands/project.py

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -349,16 +349,29 @@ async def fetch_milestones() -> dict[str, Any]:
349349
print_error(f"Project not found: {project_id}")
350350
return {}
351351

352-
result = await client.get_milestones(
353-
project_id=project_data["id"],
354-
limit=limit
355-
)
356-
return dict(result) if isinstance(result, dict) else {}
352+
# Get all milestones and filter client-side
353+
# WHY: Linear's GraphQL schema doesn't support project filtering for milestones
354+
# so we fetch all milestones and filter by project ID on the client side
355+
result = await client.get_milestones(limit=limit)
356+
if isinstance(result, dict) and "nodes" in result:
357+
# Filter milestones by project ID
358+
project_milestones = []
359+
for milestone in result["nodes"]:
360+
if milestone.get("project", {}).get("id") == project_data["id"]:
361+
project_milestones.append(milestone)
362+
363+
return {
364+
"nodes": project_milestones,
365+
"pageInfo": result.get("pageInfo", {})
366+
}
367+
368+
return {}
357369

358370
try:
359371
milestones_data = asyncio.run(fetch_milestones())
360-
if milestones_data:
361-
formatter.format_milestones(milestones_data)
372+
if milestones_data and milestones_data.get("nodes"):
373+
# Pass the nodes list to the formatter
374+
formatter.format_milestones(milestones_data["nodes"])
362375
except Exception as e:
363376
print_error(f"Failed to list project milestones: {e}")
364377
raise click.Abort() from e
@@ -388,15 +401,14 @@ def show_milestone(ctx: click.Context, project_id: str, milestone_id: str) -> No
388401
)
389402

390403
async def fetch_milestone() -> dict[str, Any] | None:
391-
# First try direct lookup
392-
milestone_data = await client.get_milestone(milestone_id)
393-
if not milestone_data:
394-
# Try resolving by name within project context
395-
resolved_id = await client.resolve_milestone_id(milestone_id, project_id)
396-
if resolved_id:
397-
milestone_data = await client.get_milestone(resolved_id)
404+
# WHY: Always resolve milestone ID first since user can provide either ID or name
405+
# Direct lookup only works with actual milestone IDs, not names
406+
resolved_id = await client.resolve_milestone_id(milestone_id, project_id)
407+
if resolved_id:
408+
milestone_data = await client.get_milestone(resolved_id)
409+
return dict(milestone_data) if milestone_data else None
398410

399-
return dict(milestone_data) if milestone_data else None
411+
return None
400412

401413
try:
402414
milestone_data = asyncio.run(fetch_milestone())

0 commit comments

Comments
 (0)