mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge pull request #1518 from FuzzkingCool/fix_missing_published_workfiles_details_in_sidepanel
Workfiles tool: Add published workfiles details
This commit is contained in:
commit
6bf7dea414
5 changed files with 278 additions and 80 deletions
|
|
@ -1,8 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from abc import ABC, abstractmethod
|
||||
import typing
|
||||
from typing import Optional
|
||||
|
||||
from ayon_core.style import get_default_entity_icon_color
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from ayon_core.host import PublishedWorkfileInfo
|
||||
|
||||
|
||||
class FolderItem:
|
||||
"""Item representing folder entity on a server.
|
||||
|
|
@ -159,6 +166,17 @@ class WorkareaFilepathResult:
|
|||
self.filepath = filepath
|
||||
|
||||
|
||||
class PublishedWorkfileWrap:
|
||||
"""Wrapper for workfile info that also contains version comment."""
|
||||
def __init__(
|
||||
self,
|
||||
info: Optional[PublishedWorkfileInfo] = None,
|
||||
comment: Optional[str] = None,
|
||||
) -> None:
|
||||
self.info = info
|
||||
self.comment = comment
|
||||
|
||||
|
||||
class AbstractWorkfilesCommon(ABC):
|
||||
@abstractmethod
|
||||
def is_host_valid(self):
|
||||
|
|
@ -787,6 +805,25 @@ class AbstractWorkfilesFrontend(AbstractWorkfilesCommon):
|
|||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_published_workfile_info(
|
||||
self,
|
||||
folder_id: Optional[str],
|
||||
representation_id: Optional[str],
|
||||
) -> PublishedWorkfileWrap:
|
||||
"""Get published workfile info by representation ID.
|
||||
|
||||
Args:
|
||||
folder_id (Optional[str]): Folder id.
|
||||
representation_id (Optional[str]): Representation id.
|
||||
|
||||
Returns:
|
||||
PublishedWorkfileWrap: Published workfile info or None
|
||||
if not found.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_workfile_info(self, folder_id, task_id, rootless_path):
|
||||
"""Workfile info from database.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import ayon_api
|
||||
|
||||
|
|
@ -18,6 +21,7 @@ from ayon_core.tools.common_models import (
|
|||
from .abstract import (
|
||||
AbstractWorkfilesBackend,
|
||||
AbstractWorkfilesFrontend,
|
||||
PublishedWorkfileWrap,
|
||||
)
|
||||
from .models import SelectionModel, WorkfilesModel
|
||||
|
||||
|
|
@ -432,6 +436,15 @@ class BaseWorkfileController(
|
|||
folder_id, task_id
|
||||
)
|
||||
|
||||
def get_published_workfile_info(
|
||||
self,
|
||||
folder_id: Optional[str],
|
||||
representation_id: Optional[str],
|
||||
) -> PublishedWorkfileWrap:
|
||||
return self._workfiles_model.get_published_workfile_info(
|
||||
folder_id, representation_id
|
||||
)
|
||||
|
||||
def get_workfile_info(self, folder_id, task_id, rootless_path):
|
||||
return self._workfiles_model.get_workfile_info(
|
||||
folder_id, task_id, rootless_path
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ class SelectionModel(object):
|
|||
self._task_name = None
|
||||
self._task_id = None
|
||||
self._workfile_path = None
|
||||
self._rootless_workfile_path = None
|
||||
self._workfile_entity_id = None
|
||||
self._representation_id = None
|
||||
|
||||
def get_selected_folder_id(self):
|
||||
|
|
@ -62,39 +64,49 @@ class SelectionModel(object):
|
|||
def get_selected_workfile_path(self):
|
||||
return self._workfile_path
|
||||
|
||||
def get_selected_workfile_data(self):
|
||||
return {
|
||||
"project_name": self._controller.get_current_project_name(),
|
||||
"path": self._workfile_path,
|
||||
"rootless_path": self._rootless_workfile_path,
|
||||
"folder_id": self._folder_id,
|
||||
"task_name": self._task_name,
|
||||
"task_id": self._task_id,
|
||||
"workfile_entity_id": self._workfile_entity_id,
|
||||
}
|
||||
|
||||
def set_selected_workfile_path(
|
||||
self, rootless_path, path, workfile_entity_id
|
||||
):
|
||||
if path == self._workfile_path:
|
||||
return
|
||||
|
||||
self._rootless_workfile_path = rootless_path
|
||||
self._workfile_path = path
|
||||
self._workfile_entity_id = workfile_entity_id
|
||||
self._controller.emit_event(
|
||||
"selection.workarea.changed",
|
||||
{
|
||||
"project_name": self._controller.get_current_project_name(),
|
||||
"path": path,
|
||||
"rootless_path": rootless_path,
|
||||
"folder_id": self._folder_id,
|
||||
"task_name": self._task_name,
|
||||
"task_id": self._task_id,
|
||||
"workfile_entity_id": workfile_entity_id,
|
||||
},
|
||||
self.get_selected_workfile_data(),
|
||||
self.event_source
|
||||
)
|
||||
|
||||
def get_selected_representation_id(self):
|
||||
return self._representation_id
|
||||
|
||||
def get_selected_representation_data(self):
|
||||
return {
|
||||
"project_name": self._controller.get_current_project_name(),
|
||||
"folder_id": self._folder_id,
|
||||
"task_id": self._task_id,
|
||||
"representation_id": self._representation_id,
|
||||
}
|
||||
|
||||
def set_selected_representation_id(self, representation_id):
|
||||
if representation_id == self._representation_id:
|
||||
return
|
||||
self._representation_id = representation_id
|
||||
self._controller.emit_event(
|
||||
"selection.representation.changed",
|
||||
{
|
||||
"project_name": self._controller.get_current_project_name(),
|
||||
"representation_id": representation_id,
|
||||
},
|
||||
self.get_selected_representation_data(),
|
||||
self.event_source
|
||||
)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ from ayon_core.pipeline.workfile import (
|
|||
from ayon_core.pipeline.version_start import get_versioning_start
|
||||
from ayon_core.tools.workfiles.abstract import (
|
||||
WorkareaFilepathResult,
|
||||
PublishedWorkfileWrap,
|
||||
AbstractWorkfilesBackend,
|
||||
)
|
||||
|
||||
|
|
@ -79,6 +80,7 @@ class WorkfilesModel:
|
|||
|
||||
# Published workfiles
|
||||
self._repre_by_id = {}
|
||||
self._version_comment_by_id = {}
|
||||
self._published_workfile_items_cache = NestedCacheItem(
|
||||
levels=1, default_factory=list
|
||||
)
|
||||
|
|
@ -95,6 +97,7 @@ class WorkfilesModel:
|
|||
self._workarea_file_items_cache.reset()
|
||||
|
||||
self._repre_by_id = {}
|
||||
self._version_comment_by_id = {}
|
||||
self._published_workfile_items_cache.reset()
|
||||
|
||||
self._workfile_entities_by_task_id = {}
|
||||
|
|
@ -552,13 +555,13 @@ class WorkfilesModel:
|
|||
)
|
||||
|
||||
def get_published_file_items(
|
||||
self, folder_id: str, task_id: str
|
||||
self, folder_id: Optional[str], task_id: Optional[str]
|
||||
) -> list[PublishedWorkfileInfo]:
|
||||
"""Published workfiles for passed context.
|
||||
|
||||
Args:
|
||||
folder_id (str): Folder id.
|
||||
task_id (str): Task id.
|
||||
folder_id (Optional[str]): Folder id.
|
||||
task_id (Optional[str]): Task id.
|
||||
|
||||
Returns:
|
||||
list[PublishedWorkfileInfo]: List of files for published workfiles.
|
||||
|
|
@ -586,7 +589,7 @@ class WorkfilesModel:
|
|||
version_entities = list(ayon_api.get_versions(
|
||||
project_name,
|
||||
product_ids=product_ids,
|
||||
fields={"id", "author", "taskId"},
|
||||
fields={"id", "author", "taskId", "attrib.comment"},
|
||||
))
|
||||
|
||||
repre_entities = []
|
||||
|
|
@ -600,6 +603,13 @@ class WorkfilesModel:
|
|||
repre_entity["id"]: repre_entity
|
||||
for repre_entity in repre_entities
|
||||
})
|
||||
|
||||
# Map versions by representation ID for easy lookup
|
||||
self._version_comment_by_id.update({
|
||||
version_entity["id"]: version_entity["attrib"].get("comment")
|
||||
for version_entity in version_entities
|
||||
})
|
||||
|
||||
project_entity = self._controller.get_project_entity(project_name)
|
||||
|
||||
prepared_data = ListPublishedWorkfilesOptionalData(
|
||||
|
|
@ -626,6 +636,34 @@ class WorkfilesModel:
|
|||
]
|
||||
return items
|
||||
|
||||
def get_published_workfile_info(
|
||||
self,
|
||||
folder_id: Optional[str],
|
||||
representation_id: Optional[str],
|
||||
) -> PublishedWorkfileWrap:
|
||||
"""Get published workfile info by representation ID.
|
||||
|
||||
Args:
|
||||
folder_id (Optional[str]): Folder id.
|
||||
representation_id (Optional[str]): Representation id.
|
||||
|
||||
Returns:
|
||||
PublishedWorkfileWrap: Published workfile info or None
|
||||
if not found.
|
||||
|
||||
"""
|
||||
if not representation_id:
|
||||
return PublishedWorkfileWrap()
|
||||
|
||||
# Search through all cached published workfile items
|
||||
for item in self.get_published_file_items(folder_id, None):
|
||||
if item.representation_id == representation_id:
|
||||
comment = self._get_published_workfile_version_comment(
|
||||
representation_id
|
||||
)
|
||||
return PublishedWorkfileWrap(item, comment)
|
||||
return PublishedWorkfileWrap()
|
||||
|
||||
@property
|
||||
def _project_name(self) -> str:
|
||||
return self._controller.get_current_project_name()
|
||||
|
|
@ -642,6 +680,25 @@ class WorkfilesModel:
|
|||
self._current_username = get_ayon_username()
|
||||
return self._current_username
|
||||
|
||||
def _get_published_workfile_version_comment(
|
||||
self, representation_id: str
|
||||
) -> Optional[str]:
|
||||
"""Get version comment for published workfile.
|
||||
|
||||
Args:
|
||||
representation_id (str): Representation id.
|
||||
|
||||
Returns:
|
||||
Optional[str]: Version comment or None.
|
||||
|
||||
"""
|
||||
if not representation_id:
|
||||
return None
|
||||
repre = self._repre_by_id.get(representation_id)
|
||||
if not repre:
|
||||
return None
|
||||
return self._version_comment_by_id.get(repre["versionId"])
|
||||
|
||||
# --- Host ---
|
||||
def _open_workfile(self, folder_id: str, task_id: str, filepath: str):
|
||||
# TODO move to workfiles pipeline
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import datetime
|
||||
from typing import Optional
|
||||
|
||||
from qtpy import QtWidgets, QtCore
|
||||
from qtpy import QtCore, QtWidgets
|
||||
|
||||
|
||||
def file_size_to_string(file_size):
|
||||
|
|
@ -8,9 +9,9 @@ def file_size_to_string(file_size):
|
|||
return "N/A"
|
||||
size = 0
|
||||
size_ending_mapping = {
|
||||
"KB": 1024 ** 1,
|
||||
"MB": 1024 ** 2,
|
||||
"GB": 1024 ** 3
|
||||
"KB": 1024**1,
|
||||
"MB": 1024**2,
|
||||
"GB": 1024**3,
|
||||
}
|
||||
ending = "B"
|
||||
for _ending, _size in size_ending_mapping.items():
|
||||
|
|
@ -70,7 +71,12 @@ class SidePanelWidget(QtWidgets.QWidget):
|
|||
btn_description_save.clicked.connect(self._on_save_click)
|
||||
|
||||
controller.register_event_callback(
|
||||
"selection.workarea.changed", self._on_selection_change
|
||||
"selection.workarea.changed",
|
||||
self._on_workarea_selection_change
|
||||
)
|
||||
controller.register_event_callback(
|
||||
"selection.representation.changed",
|
||||
self._on_representation_selection_change,
|
||||
)
|
||||
|
||||
self._details_input = details_input
|
||||
|
|
@ -82,12 +88,13 @@ class SidePanelWidget(QtWidgets.QWidget):
|
|||
self._task_id = None
|
||||
self._filepath = None
|
||||
self._rootless_path = None
|
||||
self._representation_id = None
|
||||
self._orig_description = ""
|
||||
self._controller = controller
|
||||
|
||||
self._set_context(None, None, None, None)
|
||||
self._set_context(False, None, None)
|
||||
|
||||
def set_published_mode(self, published_mode):
|
||||
def set_published_mode(self, published_mode: bool) -> None:
|
||||
"""Change published mode.
|
||||
|
||||
Args:
|
||||
|
|
@ -95,14 +102,37 @@ class SidePanelWidget(QtWidgets.QWidget):
|
|||
"""
|
||||
|
||||
self._description_widget.setVisible(not published_mode)
|
||||
# Clear the context when switching modes to avoid showing stale data
|
||||
if published_mode:
|
||||
self._set_publish_context(
|
||||
self._folder_id,
|
||||
self._task_id,
|
||||
self._representation_id,
|
||||
)
|
||||
else:
|
||||
self._set_workarea_context(
|
||||
self._folder_id,
|
||||
self._task_id,
|
||||
self._rootless_path,
|
||||
self._filepath,
|
||||
)
|
||||
|
||||
def _on_selection_change(self, event):
|
||||
def _on_workarea_selection_change(self, event):
|
||||
folder_id = event["folder_id"]
|
||||
task_id = event["task_id"]
|
||||
filepath = event["path"]
|
||||
rootless_path = event["rootless_path"]
|
||||
|
||||
self._set_context(folder_id, task_id, rootless_path, filepath)
|
||||
self._set_workarea_context(
|
||||
folder_id, task_id, rootless_path, filepath
|
||||
)
|
||||
|
||||
def _on_representation_selection_change(self, event):
|
||||
folder_id = event["folder_id"]
|
||||
task_id = event["task_id"]
|
||||
representation_id = event["representation_id"]
|
||||
|
||||
self._set_publish_context(folder_id, task_id, representation_id)
|
||||
|
||||
def _on_description_change(self):
|
||||
text = self._description_input.toPlainText()
|
||||
|
|
@ -118,85 +148,134 @@ class SidePanelWidget(QtWidgets.QWidget):
|
|||
self._orig_description = description
|
||||
self._btn_description_save.setEnabled(False)
|
||||
|
||||
def _set_context(self, folder_id, task_id, rootless_path, filepath):
|
||||
def _set_workarea_context(
|
||||
self,
|
||||
folder_id: Optional[str],
|
||||
task_id: Optional[str],
|
||||
rootless_path: Optional[str],
|
||||
filepath: Optional[str],
|
||||
) -> None:
|
||||
self._rootless_path = rootless_path
|
||||
self._filepath = filepath
|
||||
|
||||
workfile_info = None
|
||||
# Check if folder, task and file are selected
|
||||
if folder_id and task_id and rootless_path:
|
||||
workfile_info = self._controller.get_workfile_info(
|
||||
folder_id, task_id, rootless_path
|
||||
)
|
||||
enabled = workfile_info is not None
|
||||
|
||||
self._details_input.setEnabled(enabled)
|
||||
self._description_input.setEnabled(enabled)
|
||||
self._btn_description_save.setEnabled(enabled)
|
||||
|
||||
self._folder_id = folder_id
|
||||
self._task_id = task_id
|
||||
self._filepath = filepath
|
||||
self._rootless_path = rootless_path
|
||||
|
||||
# Disable inputs and remove texts if any required arguments are
|
||||
# missing
|
||||
if not enabled:
|
||||
if workfile_info is None:
|
||||
self._orig_description = ""
|
||||
self._details_input.setPlainText("")
|
||||
self._description_input.setPlainText("")
|
||||
self._set_context(False, folder_id, task_id)
|
||||
return
|
||||
|
||||
description = workfile_info.description
|
||||
size_value = file_size_to_string(workfile_info.file_size)
|
||||
self._set_context(
|
||||
True,
|
||||
folder_id,
|
||||
task_id,
|
||||
file_created=workfile_info.file_created,
|
||||
file_modified=workfile_info.file_modified,
|
||||
size_value=workfile_info.file_size,
|
||||
created_by=workfile_info.created_by,
|
||||
updated_by=workfile_info.updated_by,
|
||||
)
|
||||
|
||||
description = workfile_info.description
|
||||
self._orig_description = description
|
||||
self._description_input.setPlainText(description)
|
||||
|
||||
def _set_publish_context(
|
||||
self,
|
||||
folder_id: Optional[str],
|
||||
task_id: Optional[str],
|
||||
representation_id: Optional[str],
|
||||
) -> None:
|
||||
self._representation_id = representation_id
|
||||
published_workfile_wrap = self._controller.get_published_workfile_info(
|
||||
folder_id,
|
||||
representation_id,
|
||||
)
|
||||
info = published_workfile_wrap.info
|
||||
comment = published_workfile_wrap.comment
|
||||
if info is None:
|
||||
self._set_context(False, folder_id, task_id)
|
||||
return
|
||||
|
||||
self._set_context(
|
||||
True,
|
||||
folder_id,
|
||||
task_id,
|
||||
file_created=info.file_created,
|
||||
file_modified=info.file_modified,
|
||||
size_value=info.file_size,
|
||||
created_by=info.author,
|
||||
comment=comment,
|
||||
)
|
||||
|
||||
def _set_context(
|
||||
self,
|
||||
is_valid: bool,
|
||||
folder_id: Optional[str],
|
||||
task_id: Optional[str],
|
||||
*,
|
||||
file_created: Optional[int] = None,
|
||||
file_modified: Optional[int] = None,
|
||||
size_value: Optional[int] = None,
|
||||
created_by: Optional[str] = None,
|
||||
updated_by: Optional[str] = None,
|
||||
comment: Optional[str] = None,
|
||||
) -> None:
|
||||
self._folder_id = folder_id
|
||||
self._task_id = task_id
|
||||
|
||||
self._details_input.setEnabled(is_valid)
|
||||
self._description_input.setEnabled(is_valid)
|
||||
self._btn_description_save.setEnabled(is_valid)
|
||||
if not is_valid:
|
||||
self._details_input.setPlainText("")
|
||||
return
|
||||
|
||||
# Append html string
|
||||
datetime_format = "%b %d %Y %H:%M:%S"
|
||||
file_created = workfile_info.file_created
|
||||
modification_time = workfile_info.file_modified
|
||||
if file_created:
|
||||
file_created = datetime.datetime.fromtimestamp(file_created)
|
||||
|
||||
if modification_time:
|
||||
modification_time = datetime.datetime.fromtimestamp(
|
||||
modification_time)
|
||||
if file_modified:
|
||||
file_modified = datetime.datetime.fromtimestamp(
|
||||
file_modified
|
||||
)
|
||||
|
||||
user_items_by_name = self._controller.get_user_items_by_name()
|
||||
|
||||
def convert_username(username):
|
||||
user_item = user_items_by_name.get(username)
|
||||
def convert_username(username_v):
|
||||
user_item = user_items_by_name.get(username_v)
|
||||
if user_item is not None and user_item.full_name:
|
||||
return user_item.full_name
|
||||
return username
|
||||
return username_v
|
||||
|
||||
created_lines = []
|
||||
if workfile_info.created_by:
|
||||
created_lines.append(
|
||||
convert_username(workfile_info.created_by)
|
||||
)
|
||||
lines = []
|
||||
if size_value is not None:
|
||||
size_value = file_size_to_string(size_value)
|
||||
lines.append(f"<b>Size:</b><br/>{size_value}")
|
||||
|
||||
# Add version comment for published workfiles
|
||||
if comment:
|
||||
lines.append(f"<b>Comment:</b><br/>{comment}")
|
||||
|
||||
if created_by or file_created:
|
||||
lines.append("<b>Created:</b>")
|
||||
if created_by:
|
||||
lines.append(convert_username(created_by))
|
||||
if file_created:
|
||||
created_lines.append(file_created.strftime(datetime_format))
|
||||
lines.append(file_created.strftime(datetime_format))
|
||||
|
||||
if created_lines:
|
||||
created_lines.insert(0, "<b>Created:</b>")
|
||||
|
||||
modified_lines = []
|
||||
if workfile_info.updated_by:
|
||||
modified_lines.append(
|
||||
convert_username(workfile_info.updated_by)
|
||||
)
|
||||
if modification_time:
|
||||
modified_lines.append(
|
||||
modification_time.strftime(datetime_format)
|
||||
)
|
||||
if modified_lines:
|
||||
modified_lines.insert(0, "<b>Modified:</b>")
|
||||
|
||||
lines = (
|
||||
"<b>Size:</b>",
|
||||
size_value,
|
||||
"<br/>".join(created_lines),
|
||||
"<br/>".join(modified_lines),
|
||||
)
|
||||
self._orig_description = description
|
||||
self._description_input.setPlainText(description)
|
||||
if updated_by or file_modified:
|
||||
lines.append("<b>Modified:</b>")
|
||||
if updated_by:
|
||||
lines.append(convert_username(updated_by))
|
||||
if file_modified:
|
||||
lines.append(file_modified.strftime(datetime_format))
|
||||
|
||||
# Set as empty string
|
||||
self._details_input.setPlainText("")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue