Skip to content

Implement issue management#2043

Open
paulnoirel wants to merge 1 commit intodevelopfrom
pnoirel/PLT-2586-issue-management
Open

Implement issue management#2043
paulnoirel wants to merge 1 commit intodevelopfrom
pnoirel/PLT-2586-issue-management

Conversation

@paulnoirel
Copy link
Contributor

@paulnoirel paulnoirel commented Feb 12, 2026

Description

Adds full programmatic support for Issues, Comments, and Issue Categories. Issues can be pinned to specific data row locations (Image, PDF, Text, Video).

Usage Example

This is the standard flow for most users:

project = client.get_project("<ID>")

# Create a category and an issue pinned to an image
cat = project.create_issue_category(name="Quality", description="Quality-related issues")
issue = project.create_issue(
    content="Misaligned box",
    data_row_id="<DR_ID>",
    category_id=cat.id,
    position=ImageIssuePosition(x=100, y=200)
)

# Comment and Resolve
issue.create_comment(content="Acknowledged.")
issue.resolve()

Deep Dive: All Supported Positions

Click to see exhaustive examples for PDF, Text, and Video
# PDF (%)
PdfIssuePosition(x=0.5, y=0.75, page=2)

# Text (Char range)
TextIssuePosition(text_block_id="b1", start_char_index=10, end_char_index=25)

# Video (Multiple frames/paths)
VideoIssuePosition(frames=[
    VideoFrameRange(start=5, end=11, x=450, y=300, end_x=500, end_y=350),
    VideoFrameRange(start=20, end=25, x=100, y=100)
])

Implementation Note

Used Pydantic (_CamelCaseMixin) instead of DbObject because the backend GraphQL mutations for Issues use typed input objects incompatible with the legacy ORM's auto-generation.

Fixes # (issue)

Type of change

Please delete options that are not relevant.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Document change (fix typo or modifying any markdown files, code comments or anything in the examples folder only)

All Submissions

  • Have you followed the guidelines in our Contributing document?
  • Have you provided a description?
  • Are your changes properly formatted?

New Feature Submissions

  • Does your submission pass tests?
  • Have you added thorough tests for your new feature?
  • Have you commented your code, particularly in hard-to-understand areas?
  • Have you added a Docstring?

Changes to Core Features

  • Have you written new tests for your core changes, as applicable?
  • Have you successfully run tests with your changes locally?
  • Have you updated any code comments, as applicable?

Note

Medium Risk
Adds new GraphQL mutation/query flows (mostly experimental=True) and new typed position serialization/deserialization, so failures would primarily be API/compatibility issues rather than affecting existing flows.

Overview
Adds first-class Issue Management APIs to the Python SDK. Projects can now create_issue, get_issues (cursor-paginated with filters), get_issue, and delete_issues, while Issue objects support update, resolve/reopen, delete, and comment CRUD (create_comment, comments, Comment.update, Comment.delete).

Introduces typed issue pinning via new IssuePosition models for image/PDF/text/video (with serialization + deserialization), plus IssueCategory CRUD and new Sphinx docs and exports in labelbox.__init__. Includes comprehensive unit + integration test coverage for the new GraphQL-backed workflows (all marked experimental=True).

Written by Cursor Bugbot for commit c6f03f4. This will update automatically on new commits. Configure here.

@paulnoirel
Copy link
Contributor Author

Full API Reference with Examples

Issue Categories

project = client.get_project("<PROJECT_ID>")

# Create
category = project.create_issue_category(
    name="Quality",
    description="Quality-related issues",
)

# List all
categories = project.get_issue_categories()

# Update
category = category.update(name="Renamed", description="New description")

# Delete
category.delete()

Issues

from labelbox import IssueStatus, ImageIssuePosition, VideoIssuePosition, VideoFrameRange

# Create -- minimal
issue = project.create_issue(
    content="General quality issue",
    data_row_id="<DATA_ROW_ID>",
)

# Create -- with label, category, and position
issue = project.create_issue(
    content="Bounding box is misaligned",
    data_row_id="<DATA_ROW_ID>",
    label_id="<LABEL_ID>",
    category_id=category.id,
    position=ImageIssuePosition(x=100, y=200),
)

# Also accepts DataRow/Label/IssueCategory objects directly
issue = project.create_issue(
    content="Using objects instead of IDs",
    data_row_id=data_row,       # DataRow object
    label_id=label,             # Label object
    category_id=category,       # IssueCategory object
)

# Fetch with filters (returns PaginatedCollection)
for issue in project.get_issues(status=IssueStatus.OPEN):
    print(issue.friendly_id, issue.content)

# Other available filters
project.get_issues(
    data_row_id="<DATA_ROW_ID>",
    category_id="<CATEGORY_ID>",
    created_by_ids=["<USER_ID>"],
    content="search text",
)

# Fetch single issue
issue = project.get_issue("<ISSUE_ID>")

# Update (only provided fields are changed)
issue = issue.update(content="Updated text")
issue = issue.update(category_id="<NEW_CATEGORY_ID>")

# Resolve / Reopen
issue = issue.resolve()
issue = issue.reopen()

# Lazy-loaded related objects (each makes an API call)
data_row = issue.data_row()     # Optional[DataRow]
label = issue.label()           # Optional[Label]
category = issue.category()     # Optional[IssueCategory]

# Delete single
issue.delete()

# Bulk delete
project.delete_issues(["<ISSUE_ID_1>", "<ISSUE_ID_2>"])

Comments

# Create
comment = issue.create_comment(content="I agree, this needs fixing")

# List all comments on an issue
comments = issue.comments()

# Update
comment = comment.update(content="Revised comment")

# Delete
comment.delete()

Video Position Variants

# Single frame
VideoIssuePosition(frames=[
    VideoFrameRange(start=5, end=5, x=100, y=200)
])

# Contiguous range (stationary pin)
VideoIssuePosition(frames=[
    VideoFrameRange(start=5, end=11, x=450, y=300)
])

# Contiguous range (moving pin)
VideoIssuePosition(frames=[
    VideoFrameRange(start=5, end=11, x=450, y=300, end_x=500, end_y=350)
])

# Multiple separated ranges
VideoIssuePosition(frames=[
    VideoFrameRange(start=5, end=11, x=450, y=300),
    VideoFrameRange(start=20, end=25, x=100, y=100),
])

Co-authored-by: Cursor <cursoragent@cursor.com>
@paulnoirel paulnoirel force-pushed the pnoirel/PLT-2586-issue-management branch from a8fcd12 to c6f03f4 Compare February 15, 2026 20:07
@paulnoirel paulnoirel marked this pull request as ready for review February 23, 2026 22:33
@paulnoirel paulnoirel requested a review from a team as a code owner February 23, 2026 22:33
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

result = self._client.execute(
query_str, {"projectId": self._project_id}
)
raw_list = result.get("project", {}).get("issueCategories", [])
Copy link

Choose a reason for hiding this comment

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

Missing experimental flag on category queries

High Severity

Issue.category() and Project.get_issue_categories() call client.execute(...) without experimental=True, while the rest of the new Issues API uses experimental=True. If the backend gates these GraphQL fields behind the experimental flag, these methods will fail at runtime even though the other issue operations work.

Additional Locations (1)

Fix in Cursor Fix in Web

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.

1 participant