From d34ef8d2f869b363751be41fa6a7de86e7c61a5b Mon Sep 17 00:00:00 2001 From: Martastain Date: Tue, 23 Jul 2024 12:51:27 +0200 Subject: [PATCH 1/5] feat: reviewable sorting skeleton --- api/review/__init__.py | 4 ++-- api/review/sorting.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 api/review/sorting.py diff --git a/api/review/__init__.py b/api/review/__init__.py index 924e6349..96649d2c 100644 --- a/api/review/__init__.py +++ b/api/review/__init__.py @@ -1,4 +1,4 @@ -__all__ = ["router", "listing", "upload"] +__all__ = ["router", "listing", "sorting", "upload"] -from . import listing, upload +from . import listing, sorting, upload from .router import router diff --git a/api/review/sorting.py b/api/review/sorting.py new file mode 100644 index 00000000..6b59bf4d --- /dev/null +++ b/api/review/sorting.py @@ -0,0 +1,21 @@ +from ayon_server.api.dependencies import CurrentUser, ProjectName, VersionID +from ayon_server.types import OPModel + +from .router import router + + +class SortReviewablesRequest(OPModel): + ids: list[str] + + +@router.patch("/versions/{version_id}/reviewables") +async def sort_version_reviewables( + user: CurrentUser, project_name: ProjectName, version_id: VersionID +) -> 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. + """ + + pass From 446d0dca4599262b02a0dfcf4f8ddfb68f02011f Mon Sep 17 00:00:00 2001 From: Martastain Date: Tue, 23 Jul 2024 14:56:52 +0200 Subject: [PATCH 2/5] feat: implement manual sorting of reviewables --- api/review/listing.py | 3 ++- api/review/sorting.py | 58 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/api/review/listing.py b/api/review/listing.py index 50e77029..6c175d4e 100644 --- a/api/review/listing.py +++ b/api/review/listing.py @@ -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() diff --git a/api/review/sorting.py b/api/review/sorting.py index 6b59bf4d..17c7e909 100644 --- a/api/review/sorting.py +++ b/api/review/sorting.py @@ -1,16 +1,26 @@ from ayon_server.api.dependencies import CurrentUser, ProjectName, VersionID -from ayon_server.types import OPModel +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): - ids: list[str] + sort: list[str] | None = Field( + None, + description="List of reviewable (activity) ids in the order " + "you want them to appear in the UI.", + ) @router.patch("/versions/{version_id}/reviewables") async def sort_version_reviewables( - user: CurrentUser, project_name: ProjectName, version_id: VersionID + user: CurrentUser, + project_name: ProjectName, + version_id: VersionID, + request: SortReviewablesRequest, ) -> None: """Change the order of reviewables of a given version. @@ -18,4 +28,44 @@ async def sort_version_reviewables( in the order you want them to appear in the UI. """ - pass + 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: + print("Saved:", valid_ids) + print("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 From 40f27ab8b37aa1058dc063f231ca198b868db9e1 Mon Sep 17 00:00:00 2001 From: Martastain Date: Tue, 23 Jul 2024 15:02:32 +0200 Subject: [PATCH 3/5] chore(reviewables): renamed sorting to patching --- api/review/__init__.py | 4 ++-- api/review/{sorting.py => patching.py} | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) rename api/review/{sorting.py => patching.py} (94%) diff --git a/api/review/__init__.py b/api/review/__init__.py index 96649d2c..ca17dd3c 100644 --- a/api/review/__init__.py +++ b/api/review/__init__.py @@ -1,4 +1,4 @@ -__all__ = ["router", "listing", "sorting", "upload"] +__all__ = ["router", "listing", "patching", "upload"] -from . import listing, sorting, upload +from . import listing, patching, upload from .router import router diff --git a/api/review/sorting.py b/api/review/patching.py similarity index 94% rename from api/review/sorting.py rename to api/review/patching.py index 17c7e909..ed22c506 100644 --- a/api/review/sorting.py +++ b/api/review/patching.py @@ -1,3 +1,5 @@ +from nxtools import logging + from ayon_server.api.dependencies import CurrentUser, ProjectName, VersionID from ayon_server.entities import VersionEntity from ayon_server.exceptions import BadRequestException, NotFoundException @@ -50,8 +52,8 @@ async def sort_version_reviewables( requested_ids = set(request.sort) if requested_ids != valid_ids: - print("Saved:", valid_ids) - print("Requested:", requested_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(): From 5d1f09fb9bd2ee6b2f035cfea98b5659ebd91a39 Mon Sep 17 00:00:00 2001 From: Martastain Date: Tue, 23 Jul 2024 15:28:10 +0200 Subject: [PATCH 4/5] feat: set activity label --- api/review/patching.py | 40 ++++++++++++++++++++++++++++++++- ayon_server/api/dependencies.py | 10 +++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/api/review/patching.py b/api/review/patching.py index ed22c506..4cfaf527 100644 --- a/api/review/patching.py +++ b/api/review/patching.py @@ -1,6 +1,6 @@ from nxtools import logging -from ayon_server.api.dependencies import CurrentUser, ProjectName, VersionID +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 @@ -17,6 +17,10 @@ class SortReviewablesRequest(OPModel): ) +class UpdateReviewablesRequest(OPModel): + label: str | None = Field(None, title="Reviewable Label") + + @router.patch("/versions/{version_id}/reviewables") async def sort_version_reviewables( user: CurrentUser, @@ -71,3 +75,37 @@ async def sort_version_reviewables( ) 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: + 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, + ) diff --git a/ayon_server/api/dependencies.py b/ayon_server/api/dependencies.py index 2baf235e..33dd5472 100644 --- a/ayon_server/api/dependencies.py +++ b/ayon_server/api/dependencies.py @@ -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]: From f324f405bf9c02b09f4e0cc553b9efe0be386074 Mon Sep 17 00:00:00 2001 From: Martastain Date: Tue, 23 Jul 2024 15:33:34 +0200 Subject: [PATCH 5/5] chore(reviewables): added api examples --- api/review/patching.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/api/review/patching.py b/api/review/patching.py index 4cfaf527..ea5eb101 100644 --- a/api/review/patching.py +++ b/api/review/patching.py @@ -12,13 +12,23 @@ 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") + label: str | None = Field( + None, + title="Reviewable Label", + example="Shoulder detail", + ) @router.patch("/versions/{version_id}/reviewables") @@ -85,6 +95,11 @@ async def update_reviewable( 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)