From 00c34602e2a4789c6e042d8485d062f2371086a3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 21 May 2025 23:15:44 +0200 Subject: [PATCH 01/10] Add setting to define a minimum amount of characters required for a comment on every publish --- client/ayon_core/tools/publisher/abstract.py | 25 ++++++++++++ client/ayon_core/tools/publisher/control.py | 17 ++++++++- client/ayon_core/tools/publisher/window.py | 40 +++++++++++++++++++- server/settings/tools.py | 9 +++++ 4 files changed, 88 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/publisher/abstract.py b/client/ayon_core/tools/publisher/abstract.py index 4ed91813d3..6d0027d35d 100644 --- a/client/ayon_core/tools/publisher/abstract.py +++ b/client/ayon_core/tools/publisher/abstract.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from dataclasses import dataclass, asdict from typing import ( Optional, Dict, @@ -28,6 +29,19 @@ if TYPE_CHECKING: from .models import CreatorItem, PublishErrorInfo, InstanceItem +@dataclass +class CommentDef: + """Comment attribute definition.""" + minimum_chars_required: int + + def to_data(self): + return asdict(self) + + @classmethod + def from_data(cls, data): + return cls(**data) + + class CardMessageTypes: standard = None info = "info" @@ -135,6 +149,17 @@ class AbstractPublisherCommon(ABC): pass + @abstractmethod + def get_comment_def(self) -> CommentDef: + """Get comment attribute definition. + + This can define how the Comment field should behave, like having + a minimum amount of required characters before being allowed to + publish. + + """ + pass + class AbstractPublisherBackend(AbstractPublisherCommon): @abstractmethod diff --git a/client/ayon_core/tools/publisher/control.py b/client/ayon_core/tools/publisher/control.py index 98fdda08cf..ef2e122692 100644 --- a/client/ayon_core/tools/publisher/control.py +++ b/client/ayon_core/tools/publisher/control.py @@ -20,7 +20,8 @@ from .models import ( from .abstract import ( AbstractPublisherBackend, AbstractPublisherFrontend, - CardMessageTypes + CardMessageTypes, + CommentDef, ) @@ -601,3 +602,17 @@ class PublisherController( def _start_publish(self, up_validation): self._publish_model.set_publish_up_validation(up_validation) self._publish_model.start_publish(wait=True) + + def get_comment_def(self) -> CommentDef: + # Take the cached settings from the Create Context + settings = self.get_create_context().get_current_project_settings() + comment_minimum_required_chars: int = ( + settings + .get("core", {}) + .get("tools", {}) + .get("publish", {}) + .get("comment_minimum_required_chars", 0) + ) + return CommentDef( + minimum_chars_required=comment_minimum_required_chars + ) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index ed5b909a55..a967aef21c 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -245,6 +245,12 @@ class PublisherWindow(QtWidgets.QDialog): show_timer.setInterval(1) show_timer.timeout.connect(self._on_show_timer) + comment_invalid_timer = QtCore.QTimer() + comment_invalid_timer.setInterval(2500) + comment_invalid_timer.timeout.connect( + self._on_comment_invalid_timeout + ) + errors_dialog_message_timer = QtCore.QTimer() errors_dialog_message_timer.setInterval(100) errors_dialog_message_timer.timeout.connect( @@ -395,6 +401,7 @@ class PublisherWindow(QtWidgets.QDialog): self._app_event_listener_installed = False self._show_timer = show_timer + self._comment_invalid_timer = comment_invalid_timer self._show_counter = 0 self._window_is_visible = False @@ -823,15 +830,44 @@ class PublisherWindow(QtWidgets.QDialog): self._controller.set_comment(self._comment_input.text()) def _on_validate_clicked(self): - if self._save_changes(False): + if self._start_publish_preflight() and self._save_changes(False): self._set_publish_comment() self._controller.validate() def _on_publish_clicked(self): - if self._save_changes(False): + if self._start_publish_preflight() and self._save_changes(False): self._set_publish_comment() self._controller.publish() + def _start_publish_preflight(self) -> bool: + # Validate comment length + comment_def = self.controller.get_comment_def() + if comment_def.minimum_chars_required: + char_count = len(self._comment_input.text()) + if char_count < comment_def.minimum_chars_required: + self._overlay_object.add_message( + "Please enter a comment of at least " + f"{comment_def.minimum_chars_required} characters", + message_type="error" + ) + self._invalidate_comment_field() + return False + return True + + def _invalidate_comment_field(self): + self._comment_invalid_timer.start() + self._comment_input.setStyleSheet( + "border-style: solid;" + "border-width: 1px;" + "border-color: #DD2020" + ) + # Set focus so user can start typing and is pointed towards the field + self._comment_input.setFocus() + + def _on_comment_invalid_timeout(self): + # Reset style + self._comment_input.setStyleSheet("") + def _set_footer_enabled(self, enabled): self._save_btn.setEnabled(True) self._reset_btn.setEnabled(True) diff --git a/server/settings/tools.py b/server/settings/tools.py index 6b07910454..2b33efaa8b 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -342,6 +342,14 @@ class CustomStagingDirProfileModel(BaseSettingsModel): class PublishToolModel(BaseSettingsModel): + comment_minimum_required_chars: int = SettingsField( + 0, + title="Publish comment minimum required characters", + description=( + "Minimum number of characters required in the comment field " + "before the publisher UI is allowed to continue publishing" + ) + ) template_name_profiles: list[PublishTemplateNameProfile] = SettingsField( default_factory=list, title="Template name profiles" @@ -570,6 +578,7 @@ DEFAULT_TOOLS_VALUES = { "product_type_filter_profiles": [] }, "publish": { + "comment_minimum_required_chars": 0, "template_name_profiles": [ { "product_types": [], From 2047e38aae273fc2106b864744000e7af60b47c8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 21 May 2025 23:28:50 +0200 Subject: [PATCH 02/10] Access through the private member --- client/ayon_core/tools/publisher/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index a967aef21c..4932b578e2 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -841,7 +841,7 @@ class PublisherWindow(QtWidgets.QDialog): def _start_publish_preflight(self) -> bool: # Validate comment length - comment_def = self.controller.get_comment_def() + comment_def = self._controller.get_comment_def() if comment_def.minimum_chars_required: char_count = len(self._comment_input.text()) if char_count < comment_def.minimum_chars_required: From 7413f05adb0a04b739909533a083dc5066943876 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 May 2025 22:15:24 +0200 Subject: [PATCH 03/10] Change staging dir persistence state to debug log instead of info to avoid clutter for the artist (artist doesn't care) --- .../plugins/publish/collect_managed_staging_dir.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_managed_staging_dir.py b/client/ayon_core/plugins/publish/collect_managed_staging_dir.py index 1034b9a716..62b007461a 100644 --- a/client/ayon_core/plugins/publish/collect_managed_staging_dir.py +++ b/client/ayon_core/plugins/publish/collect_managed_staging_dir.py @@ -32,16 +32,16 @@ class CollectManagedStagingDir(pyblish.api.InstancePlugin): label = "Collect Managed Staging Directory" order = pyblish.api.CollectorOrder + 0.4990 - def process(self, instance): + def process(self, instance: pyblish.api.Instance): """ Collect the staging data and stores it to the instance. Args: instance (object): The instance to inspect. """ staging_dir_path = get_instance_staging_dir(instance) - persistance = instance.data.get("stagingDir_persistent", False) + persistence: bool = instance.data.get("stagingDir_persistent", False) - self.log.info(( + self.log.debug( f"Instance staging dir was set to `{staging_dir_path}` " - f"and persistence is set to `{persistance}`" - )) + f"and persistence is set to `{persistence}`" + ) From b5b661e9829f4c91f9a6057b3dc6da66b442a2ee Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 May 2025 23:11:53 +0200 Subject: [PATCH 04/10] Move attribute down (less important) --- server/settings/tools.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server/settings/tools.py b/server/settings/tools.py index 2b33efaa8b..815ef40f8e 100644 --- a/server/settings/tools.py +++ b/server/settings/tools.py @@ -342,14 +342,6 @@ class CustomStagingDirProfileModel(BaseSettingsModel): class PublishToolModel(BaseSettingsModel): - comment_minimum_required_chars: int = SettingsField( - 0, - title="Publish comment minimum required characters", - description=( - "Minimum number of characters required in the comment field " - "before the publisher UI is allowed to continue publishing" - ) - ) template_name_profiles: list[PublishTemplateNameProfile] = SettingsField( default_factory=list, title="Template name profiles" @@ -366,6 +358,14 @@ class PublishToolModel(BaseSettingsModel): title="Custom Staging Dir Profiles" ) ) + comment_minimum_required_chars: int = SettingsField( + 0, + title="Publish comment minimum required characters", + description=( + "Minimum number of characters required in the comment field " + "before the publisher UI is allowed to continue publishing" + ) + ) class GlobalToolsModel(BaseSettingsModel): @@ -578,7 +578,6 @@ DEFAULT_TOOLS_VALUES = { "product_type_filter_profiles": [] }, "publish": { - "comment_minimum_required_chars": 0, "template_name_profiles": [ { "product_types": [], @@ -680,6 +679,7 @@ DEFAULT_TOOLS_VALUES = { "task_names": [], "template_name": "simpleUnrealTextureHero" } - ] + ], + "comment_minimum_required_chars": 0, } } From 1500c1cc331b3882ae3e78138915e6fe7df84dea Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 May 2025 23:12:52 +0200 Subject: [PATCH 05/10] Remove redundant stylesheet overrides --- client/ayon_core/tools/publisher/window.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index 4932b578e2..6c856d8c3a 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -856,11 +856,7 @@ class PublisherWindow(QtWidgets.QDialog): def _invalidate_comment_field(self): self._comment_invalid_timer.start() - self._comment_input.setStyleSheet( - "border-style: solid;" - "border-width: 1px;" - "border-color: #DD2020" - ) + self._comment_input.setStyleSheet("border-color: #DD2020") # Set focus so user can start typing and is pointed towards the field self._comment_input.setFocus() From e35f3a59412feddc977e04390645abd79a2c282c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 May 2025 23:14:11 +0200 Subject: [PATCH 06/10] Update client/ayon_core/tools/publisher/window.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/tools/publisher/window.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index 4932b578e2..523281e193 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -842,16 +842,18 @@ class PublisherWindow(QtWidgets.QDialog): def _start_publish_preflight(self) -> bool: # Validate comment length comment_def = self._controller.get_comment_def() - if comment_def.minimum_chars_required: - char_count = len(self._comment_input.text()) - if char_count < comment_def.minimum_chars_required: - self._overlay_object.add_message( - "Please enter a comment of at least " - f"{comment_def.minimum_chars_required} characters", - message_type="error" - ) - self._invalidate_comment_field() - return False + char_count = len(self._comment_input.text()) + if ( + comment_def.minimum_chars_required + and char_count < comment_def.minimum_chars_required + ): + self._overlay_object.add_message( + "Please enter a comment of at least " + f"{comment_def.minimum_chars_required} characters", + message_type="error" + ) + self._invalidate_comment_field() + return False return True def _invalidate_comment_field(self): From d18e2622b72d5ad592b81880b244ad930228a7cc Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 May 2025 23:15:40 +0200 Subject: [PATCH 07/10] Rename to `_validate_comment` --- client/ayon_core/tools/publisher/window.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index 6c856d8c3a..13d98a5398 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -830,16 +830,16 @@ class PublisherWindow(QtWidgets.QDialog): self._controller.set_comment(self._comment_input.text()) def _on_validate_clicked(self): - if self._start_publish_preflight() and self._save_changes(False): + if self._validate_comment() and self._save_changes(False): self._set_publish_comment() self._controller.validate() def _on_publish_clicked(self): - if self._start_publish_preflight() and self._save_changes(False): + if self._validate_comment() and self._save_changes(False): self._set_publish_comment() self._controller.publish() - def _start_publish_preflight(self) -> bool: + def _validate_comment(self) -> bool: # Validate comment length comment_def = self._controller.get_comment_def() if comment_def.minimum_chars_required: From f99550a74e0870eb8dbf638cf5017adce8446bd5 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 22 May 2025 23:20:03 +0200 Subject: [PATCH 08/10] Count chars with the comment input stripped - so that e.g. solely spaces (or trailing/starting spaces) do not count to the minimal required characters --- client/ayon_core/tools/publisher/window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index 28c40d8b5a..ccc91e599f 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -842,7 +842,7 @@ class PublisherWindow(QtWidgets.QDialog): def _validate_comment(self) -> bool: # Validate comment length comment_def = self._controller.get_comment_def() - char_count = len(self._comment_input.text()) + char_count = len(self._comment_input.text().strip()) if ( comment_def.minimum_chars_required and char_count < comment_def.minimum_chars_required From b1bf4df9e00f8ab23260ea7bedba3400db782281 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 May 2025 10:45:13 +0200 Subject: [PATCH 09/10] Update client/ayon_core/tools/publisher/window.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/tools/publisher/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index ccc91e599f..af8ae13b8a 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -246,6 +246,7 @@ class PublisherWindow(QtWidgets.QDialog): show_timer.timeout.connect(self._on_show_timer) comment_invalid_timer = QtCore.QTimer() + comment_invalid_timer.setSingleShot(True) comment_invalid_timer.setInterval(2500) comment_invalid_timer.timeout.connect( self._on_comment_invalid_timeout From 539467d9ee206be109e2fc047599384457e694ce Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 23 May 2025 11:46:01 +0200 Subject: [PATCH 10/10] Set cursor at end of any existing text so that the user can directly continue with their comment Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/tools/publisher/window.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index af8ae13b8a..dc086a3b48 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -862,6 +862,9 @@ class PublisherWindow(QtWidgets.QDialog): self._comment_input.setStyleSheet("border-color: #DD2020") # Set focus so user can start typing and is pointed towards the field self._comment_input.setFocus() + self._comment_input.setCursorPosition( + len(self._comment_input.text()) + ) def _on_comment_invalid_timeout(self): # Reset style