Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Review: Reviewables sorting and patching #291

Merged
merged 5 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/review/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__all__ = ["router", "listing", "upload"]
__all__ = ["router", "listing", "patching", "upload"]

from . import listing, upload
from . import listing, patching, upload
from .router import router
3 changes: 2 additions & 1 deletion api/review/listing.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ async def get_reviewables(

ORDER BY
versions.version ASC,
af.created_at ASC
COALESCE(af.activity_data->>'reviewableOrder', '0')::integer ASC,
af.creation_order ASC
"""

processed: set[str] = set()
Expand Down
126 changes: 126 additions & 0 deletions api/review/patching.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from nxtools import logging

from ayon_server.api.dependencies import ActivityID, CurrentUser, ProjectName, VersionID
from ayon_server.entities import VersionEntity
from ayon_server.exceptions import BadRequestException, NotFoundException
from ayon_server.lib.postgres import Postgres
from ayon_server.types import Field, OPModel

from .router import router


class SortReviewablesRequest(OPModel):
sort: list[str] | None = Field(
None,
title="Reviewables Order",
description="List of reviewable (activity) ids in the order "
"you want them to appear in the UI.",
example=[
"c197712a48ef11ef95450242ac1f0004",
"c519a3f448ef11ef95450242ac1f0004",
"c8edce0648ef11ef95450242ac1f0004",
],
)


class UpdateReviewablesRequest(OPModel):
label: str | None = Field(
None,
title="Reviewable Label",
example="Shoulder detail",
)


@router.patch("/versions/{version_id}/reviewables")
async def sort_version_reviewables(
user: CurrentUser,
project_name: ProjectName,
version_id: VersionID,
request: SortReviewablesRequest,
) -> None:
"""Change the order of reviewables of a given version.

In the payload, provide a list of activity ids (reviewables)
in the order you want them to appear in the UI.
"""

version = await VersionEntity.load(project_name, version_id)
await version.ensure_update_access(user)

res = await Postgres.fetch(
f"""
SELECT activity_id FROM project_{project_name}.activity_feed
WHERE reference_type = 'origin'
AND activity_type = 'reviewable'
AND entity_type = 'version'
AND entity_id = $1
""",
version_id,
)

if not res:
raise NotFoundException(detail="Version not found")

if request.sort is not None:
valid_ids = {row["activity_id"] for row in res}
requested_ids = set(request.sort)

if requested_ids != valid_ids:
logging.debug("Saved:", valid_ids)
logging.debug("Requested:", requested_ids)
raise BadRequestException(detail="Invalid reviewable ids")

async with Postgres.acquire() as conn, conn.transaction():
for i, activity_id in enumerate(request.sort):
await Postgres.execute(
f"""
UPDATE project_{project_name}.activities
SET data = data || jsonb_build_object(
'reviewableOrder', $1::integer
)
WHERE id = $2
""",
i,
activity_id,
)

return None


@router.patch("/versions/{version_id}/reviewables/{activity_id}")
async def update_reviewable(
user: CurrentUser,
project_name: ProjectName,
version_id: VersionID,
reviewable_id: ActivityID,
request: UpdateReviewablesRequest,
) -> None:
"""Update an existing reviewable,

Currently it is only possible to update the label of a reviewable.
"""

version = await VersionEntity.load(project_name, version_id)
await version.ensure_update_access(user)

if request.label is not None:
if not request.label:
raise BadRequestException(detail="Label cannot be empty")
if not isinstance(request.label, str):
raise BadRequestException(detail="Label must be a string")
if not 0 < len(request.label) <= 255:
raise BadRequestException(
detail="Label must be between 1 and 255 characters"
)

await Postgres.execute(
f"""
UPDATE project_{project_name}.activities
SET data = data || jsonb_build_object(
'reviewableLabel', $1::text
)
WHERE id = $2
""",
request.label,
reviewable_id,
)
10 changes: 10 additions & 0 deletions ayon_server/api/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,16 @@ async def dep_link_id(
LinkID = Annotated[str, Depends(dep_link_id)]


async def dep_activity_id(
activity_id: str = Path(..., title="Activity ID", **EntityID.META),
) -> str:
"""Validate and return an activity id specified in an endpoint path."""
return activity_id


ActivityID = Annotated[str, Depends(dep_activity_id)]


async def dep_link_type(
link_type: str = Path(..., title="Link Type"),
) -> tuple[str, str, str]:
Expand Down