Add job_ids filter param to GET /api/assets

- Remove [cloud-only] marker from job_ids param in openapi.yaml
- Add job_ids field to ListAssetsQuery schema with CSV/list parsing
- Pass job_ids through route -> service -> query layer
- Filter AssetReference by job_id IN (...) on both data and count queries

Co-authored-by: Matt Miller <MillerMedia@users.noreply.github.com>
This commit is contained in:
Cursor Agent 2026-05-12 03:39:05 +00:00
parent 0a7d2ffd68
commit 4f86901dc6
No known key found for this signature in database
5 changed files with 27 additions and 2 deletions

View File

@ -213,6 +213,7 @@ async def list_assets_route(request: web.Request) -> web.Response:
owner_id=USER_MANAGER.get_request_user_id(request), owner_id=USER_MANAGER.get_request_user_id(request),
include_tags=q.include_tags, include_tags=q.include_tags,
exclude_tags=q.exclude_tags, exclude_tags=q.exclude_tags,
job_ids=q.job_ids,
name_contains=q.name_contains, name_contains=q.name_contains,
metadata_filter=q.metadata_filter, metadata_filter=q.metadata_filter,
limit=q.limit, limit=q.limit,

View File

@ -52,6 +52,7 @@ class ParsedUpload:
class ListAssetsQuery(BaseModel): class ListAssetsQuery(BaseModel):
include_tags: list[str] = Field(default_factory=list) include_tags: list[str] = Field(default_factory=list)
exclude_tags: list[str] = Field(default_factory=list) exclude_tags: list[str] = Field(default_factory=list)
job_ids: list[str] = Field(default_factory=list)
name_contains: str | None = None name_contains: str | None = None
# Accept either a JSON string (query param) or a dict # Accept either a JSON string (query param) or a dict
@ -65,6 +66,21 @@ class ListAssetsQuery(BaseModel):
) )
order: Literal["asc", "desc"] = "desc" order: Literal["asc", "desc"] = "desc"
@field_validator("job_ids", mode="before")
@classmethod
def _split_csv_job_ids(cls, v):
if v is None:
return []
if isinstance(v, str):
return [t.strip() for t in v.split(",") if t.strip()]
if isinstance(v, list):
out: list[str] = []
for item in v:
if isinstance(item, str):
out.extend([t.strip() for t in item.split(",") if t.strip()])
return out
return v
@field_validator("include_tags", "exclude_tags", mode="before") @field_validator("include_tags", "exclude_tags", mode="before")
@classmethod @classmethod
def _split_csv_tags(cls, v): def _split_csv_tags(cls, v):

View File

@ -263,6 +263,7 @@ def list_references_page(
name_contains: str | None = None, name_contains: str | None = None,
include_tags: Sequence[str] | None = None, include_tags: Sequence[str] | None = None,
exclude_tags: Sequence[str] | None = None, exclude_tags: Sequence[str] | None = None,
job_ids: Sequence[str] | None = None,
metadata_filter: dict | None = None, metadata_filter: dict | None = None,
sort: str | None = None, sort: str | None = None,
order: str | None = None, order: str | None = None,
@ -284,6 +285,9 @@ def list_references_page(
escaped, esc = escape_sql_like_string(name_contains) escaped, esc = escape_sql_like_string(name_contains)
base = base.where(AssetReference.name.ilike(f"%{escaped}%", escape=esc)) base = base.where(AssetReference.name.ilike(f"%{escaped}%", escape=esc))
if job_ids:
base = base.where(AssetReference.job_id.in_(job_ids))
base = apply_tag_filters(base, include_tags, exclude_tags) base = apply_tag_filters(base, include_tags, exclude_tags)
base = apply_metadata_filter(base, metadata_filter) base = apply_metadata_filter(base, metadata_filter)
@ -314,6 +318,9 @@ def list_references_page(
count_stmt = count_stmt.where( count_stmt = count_stmt.where(
AssetReference.name.ilike(f"%{escaped}%", escape=esc) AssetReference.name.ilike(f"%{escaped}%", escape=esc)
) )
if job_ids:
count_stmt = count_stmt.where(AssetReference.job_id.in_(job_ids))
count_stmt = apply_tag_filters(count_stmt, include_tags, exclude_tags) count_stmt = apply_tag_filters(count_stmt, include_tags, exclude_tags)
count_stmt = apply_metadata_filter(count_stmt, metadata_filter) count_stmt = apply_metadata_filter(count_stmt, metadata_filter)

View File

@ -246,6 +246,7 @@ def list_assets_page(
owner_id: str = "", owner_id: str = "",
include_tags: Sequence[str] | None = None, include_tags: Sequence[str] | None = None,
exclude_tags: Sequence[str] | None = None, exclude_tags: Sequence[str] | None = None,
job_ids: Sequence[str] | None = None,
name_contains: str | None = None, name_contains: str | None = None,
metadata_filter: dict | None = None, metadata_filter: dict | None = None,
limit: int = 20, limit: int = 20,
@ -259,6 +260,7 @@ def list_assets_page(
owner_id=owner_id, owner_id=owner_id,
include_tags=include_tags, include_tags=include_tags,
exclude_tags=exclude_tags, exclude_tags=exclude_tags,
job_ids=job_ids,
name_contains=name_contains, name_contains=name_contains,
metadata_filter=metadata_filter, metadata_filter=metadata_filter,
limit=limit, limit=limit,

View File

@ -1553,8 +1553,7 @@ paths:
in: query in: query
schema: schema:
type: string type: string
x-runtime: [cloud] description: "Comma-separated UUIDs to filter assets by associated job."
description: "[cloud-only] Comma-separated UUIDs to filter assets by associated job."
- name: include_public - name: include_public
in: query in: query
schema: schema: