From 11f8431e890b6fa72fa7fa84169c4d56b6df25c1 Mon Sep 17 00:00:00 2001 From: Joseph Yu Date: Mon, 9 Feb 2026 17:49:20 +0000 Subject: [PATCH 1/5] Initial hook and settings config --- app.py | 13 +++++++++++++ info.yml | 11 +++++++++++ 2 files changed, 24 insertions(+) diff --git a/app.py b/app.py index 18d5f0d6..17b261eb 100644 --- a/app.py +++ b/app.py @@ -83,6 +83,19 @@ def base_hooks(self): """ return self._base_hooks + @property + def summary_hook(self) -> sgtk.Hook: + """Exposes the extensible ``:summary:hook`` instance from app settings. + + Used to fetch information related to summary overlay display content. + """ + self._summary_hook = getattr( + self, + "_summary_hook", + self.create_hook_instance(self.get_setting("summary")["hook"]), + ) + return self._summary_hook + @property def util(self): """ diff --git a/info.yml b/info.yml index 87f8d7e5..2f49db31 100644 --- a/info.yml +++ b/info.yml @@ -15,6 +15,17 @@ configuration: description: Specify the name that should be used in menus and the main publish dialog + summary: + type: dict + description: Hook and settings that defines how the summary overlay is displayed + items: + hook: {type: hook} + settings: {type: dict, allows_empty: True} + default_value: + hook: "{self}/summary_hook.py" + settings: + + display_action_name: type: str default_value: Publish From 4572007b611e064cca7a041cc5d04cac31e8cc32 Mon Sep 17 00:00:00 2001 From: Joseph Yu Date: Mon, 9 Feb 2026 19:04:24 +0000 Subject: [PATCH 2/5] Initial work --- hooks/summary_hook.py | 61 +++++++++++++ info.yml | 21 +++-- python/tk_multi_publish2/__init__.py | 1 + python/tk_multi_publish2/summary_overlay.py | 63 +++++++------- tests/test_summary_hook.py | 96 +++++++++++++++++++++ 5 files changed, 197 insertions(+), 45 deletions(-) create mode 100644 hooks/summary_hook.py create mode 100644 tests/test_summary_hook.py diff --git a/hooks/summary_hook.py b/hooks/summary_hook.py new file mode 100644 index 00000000..1c76bd4a --- /dev/null +++ b/hooks/summary_hook.py @@ -0,0 +1,61 @@ +# Copyright (c) 2026 Autodesk. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the ShotGrid Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the ShotGrid Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Autodesk. +"""Hook that defines the summary overlay content and display details.""" + +from typing import Any + +import sgtk + +HookBaseClass = sgtk.get_hook_baseclass() + + +class SummaryHook(HookBaseClass): + """Hook that defines the summary overlay content and display details.""" + + @property + def settings(self) -> dict[str, Any]: + """Return the settings that are available for this hook.""" + return self.parent.get_setting("summary").get("settings") or {} + + def no_items_error(self, summary_overlay) -> dict[str, Any]: + """Return UI values for the no items collected summary state.""" + return summary_overlay.show_summary( + ":/tk_multi_publish2/publish_failed.png", + # Hardcoding line break so the message displays on 2 lines. + # Usage of label's own word wrap displays the message below on 3 lines. + # NOTE: Can't manually break line when using

+ "Could not find any\nitems to publish.", + "For more details, click here.", + ) + + def success(self, summary_overlay) -> dict[str, Any]: + """Return UI values for the publish success summary state.""" + return summary_overlay.show_summary( + ":/tk_multi_publish2/publish_complete.png", + "Publish\nComplete", + "For more details, click here.", + publish_again_text="To publish again, click here.", + ) + + def fail(self, summary_overlay) -> dict[str, Any]: + """Return UI values for the publish fail summary state.""" + return summary_overlay.show_summary( + ":/tk_multi_publish2/publish_failed.png", + "Publish\nFailed!", + "For more details, click here.", + ) + + def loading(self, summary_overlay) -> dict[str, Any]: + """Return UI values for the loading summary state.""" + return summary_overlay.show_summary( + ":/tk_multi_publish2/overlay_loading.png", + "Loading and processing", + "Hold tight while we analyze your data", + ) diff --git a/info.yml b/info.yml index 2f49db31..4fe5c0c4 100644 --- a/info.yml +++ b/info.yml @@ -15,17 +15,6 @@ configuration: description: Specify the name that should be used in menus and the main publish dialog - summary: - type: dict - description: Hook and settings that defines how the summary overlay is displayed - items: - hook: {type: hook} - settings: {type: dict, allows_empty: True} - default_value: - hook: "{self}/summary_hook.py" - settings: - - display_action_name: type: str default_value: Publish @@ -41,6 +30,16 @@ configuration: description: "Collector-specific configuration settings." default_value: {} + summary: + type: dict + description: Hook and settings that defines how the summary overlay is displayed + items: + hook: {type: hook} + settings: {type: dict, allows_empty: True} + default_value: + hook: "{self}/summary_hook.py" + settings: {} + post_phase: type: hook description: diff --git a/python/tk_multi_publish2/__init__.py b/python/tk_multi_publish2/__init__.py index ebca3c6f..d1133551 100644 --- a/python/tk_multi_publish2/__init__.py +++ b/python/tk_multi_publish2/__init__.py @@ -14,6 +14,7 @@ from . import base_hooks # noqa from . import util # noqa from . import publish_tree_widget # noqa +from . import summary_overlay # noqa def show_dialog(app): diff --git a/python/tk_multi_publish2/summary_overlay.py b/python/tk_multi_publish2/summary_overlay.py index 2533316b..bd1bab45 100644 --- a/python/tk_multi_publish2/summary_overlay.py +++ b/python/tk_multi_publish2/summary_overlay.py @@ -38,6 +38,7 @@ def __init__(self, parent): super().__init__(parent) self._bundle = sgtk.platform.current_bundle() + self._summary_hook = self._bundle.summary_hook # set up the UI self.ui = Ui_SummaryOverlay() @@ -54,60 +55,54 @@ def __init__(self, parent): self.ui.info.clicked.connect(self.info_clicked.emit) self.ui.publish_again.clicked.connect(self.publish_again_clicked.emit) + def show_summary( + self, + icon_path: str, + label_text: str, + info_text: str, + publish_again_text: str = "", + ): + """Show summary with given messaging and icon. + + :param icon_path: Path/value used directly to construct icon's QPixmap. + :param label_text: Main label text to display + :param info_text: Information text for the info button/label + :param publish_again_text: Text to show the publish again button with. + If empty (default) the button will be hidden. + """ + self.ui.icon.setPixmap(QtGui.QPixmap(icon_path)) + self.ui.label.setText(label_text) + self.ui.info.setText(info_text) + + self.ui.publish_again.setText(publish_again_text or "") + self.ui.publish_again.setVisible(bool(publish_again_text)) + + self.show() + def show_no_items_error(self): """ Shows a special message when there is no items collected under an alternate UI operation determined by the 'enable_manual_load' application option. """ - self.ui.icon.setPixmap(QtGui.QPixmap(":/tk_multi_publish2/publish_failed.png")) - # Hardcoding line break so the message displays on 2 lines. - # Usage of label's own word wrap displays the message below on 3 lines. - # NOTE: Can't manually break line when using

- self.ui.label.setText("Could not find any\nitems to publish.") - self.ui.info.setText("For more details, click here.") - self.ui.publish_again.hide() - self.show() + self._summary_hook.no_items_error(self) def show_success(self): """ Shows standard "publish completed successfully!" prompt """ - self.ui.icon.setPixmap( - QtGui.QPixmap(":/tk_multi_publish2/publish_complete.png") - ) - self.ui.label.setText("Publish\nComplete") - self.ui.info.setText("For more details, click here.") - - self.ui.publish_again.setText("To publish again, click here.") - self.ui.publish_again.show() - - self.show() + self._summary_hook.success(self) def show_fail(self): """ Shows standard "publish failed!" prompt """ - self.ui.icon.setPixmap(QtGui.QPixmap(":/tk_multi_publish2/publish_failed.png")) - self.ui.label.setText("Publish\nFailed!") - self.ui.info.setText("For more details, click here.") - - self.ui.publish_again.hide() - self.ui.publish_again.setText("") - - self.show() + self._summary_hook.fail(self) def show_loading(self): """ Shows standard "loading stuff" prompt """ - self.ui.icon.setPixmap(QtGui.QPixmap(":/tk_multi_publish2/overlay_loading.png")) - self.ui.label.setText("Loading and processing") - self.ui.info.setText("Hold tight while we analyze your data") - - self.ui.publish_again.hide() - self.ui.publish_again.setText("") - - self.show() + self._summary_hook.loading(self) def show(self): """ diff --git a/tests/test_summary_hook.py b/tests/test_summary_hook.py new file mode 100644 index 00000000..4adecb9c --- /dev/null +++ b/tests/test_summary_hook.py @@ -0,0 +1,96 @@ +# Copyright (c) 2026 Autodesk. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the ShotGrid Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the ShotGrid Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Autodesk. + +from unittest import mock + +from publish_api_test_base import PublishApiTestBase +from tank_test.tank_test_base import setUpModule # noqa + + +class TestSummaryHook(PublishApiTestBase): + def check_values_match_v2_10_7(self, summary_method, expected_args): + """Test that calling method sets expected values.""" + from sgtk.platform.qt import QtGui + + publish2 = self.app.import_module("tk_multi_publish2") + widget = publish2.summary_overlay.SummaryOverlay(QtGui.QWidget()) + with ( + mock.patch.object(widget, "show"), + mock.patch.object(QtGui, "QPixmap"), + mock.patch.object(widget.ui.icon, "setPixmap"), + mock.patch.object(widget.ui.label, "setText"), + mock.patch.object(widget.ui.info, "setText"), + mock.patch.object(widget.ui.publish_again, "setText"), + mock.patch.object(widget.ui.publish_again, "setVisible"), + ): + patched_methods_expected_args = zip( + [ + QtGui.QPixmap, + widget.ui.label.setText, + widget.ui.info.setText, + widget.ui.publish_again.setText, + widget.ui.publish_again.setVisible, + ], + expected_args, + ) + + getattr(self.app.summary_hook, summary_method)(widget) + + assert widget.ui.icon.setPixmap.called + for patched_method, expected_arg in patched_methods_expected_args: + patched_method.assert_called_with(expected_arg) + + def test_no_item_error(self): + self.check_values_match_v2_10_7( + "no_items_error", + [ + ":/tk_multi_publish2/publish_failed.png", + "Could not find any\nitems to publish.", + "For more details, click here.", + "", + False, + ], + ) + + def test_success(self): + self.check_values_match_v2_10_7( + "success", + [ + ":/tk_multi_publish2/publish_complete.png", + "Publish\nComplete", + "For more details, click here.", + "To publish again, click here.", + True, + ], + ) + + def test_fail(self): + self.check_values_match_v2_10_7( + "fail", + [ + ":/tk_multi_publish2/publish_failed.png", + "Publish\nFailed!", + "For more details, click here.", + "", + False, + ], + ) + + def test_loading(self): + self.check_values_match_v2_10_7( + "loading", + [ + ":/tk_multi_publish2/overlay_loading.png", + "Loading and processing", + "Hold tight while we analyze your data", + "", + False, + ], + ) From 1739993cc013ba8a90ce82696f3f420c694b9baf Mon Sep 17 00:00:00 2001 From: Joseph Yu Date: Mon, 9 Feb 2026 19:43:54 +0000 Subject: [PATCH 3/5] Tidied --- hooks/summary_hook.py | 70 ++++++++++----- info.yml | 28 +++++- ...ummary_hook.py => test_summary_overlay.py} | 85 +++++++++---------- 3 files changed, 116 insertions(+), 67 deletions(-) rename tests/{test_summary_hook.py => test_summary_overlay.py} (53%) diff --git a/hooks/summary_hook.py b/hooks/summary_hook.py index 1c76bd4a..51aaf829 100644 --- a/hooks/summary_hook.py +++ b/hooks/summary_hook.py @@ -19,43 +19,67 @@ class SummaryHook(HookBaseClass): """Hook that defines the summary overlay content and display details.""" + FALLBACK_SETTINGS = { + "no_item_error": { + "icon_path": ":/tk_multi_publish2/publish_failed.png", + # Hardcoding line break so the message displays on 2 lines. + # Usage of label's own word wrap displays the message below on 3 lines. + # NOTE: Can't manually break line when using

+ "label_text": "Could not find any\nitems to publish.", + "info_text": "For more details, click here.", + "publish_again_text": "", + }, + "success": { + "icon_path": ":/tk_multi_publish2/publish_complete.png", + "label_text": "Publish\nComplete", + "info_text": "For more details, click here.", + "publish_again_text": "To publish again, click here.", + }, + "fail": { + "icon_path": ":/tk_multi_publish2/publish_failed.png", + "label_text": "Publish\nFailed!", + "info_text": "For more details, click here.", + "publish_again_text": "", + }, + "loading": { + "icon_path": ":/tk_multi_publish2/overlay_loading.png", + "label_text": "Loading and processing", + "info_text": "Hold tight while we analyze your data", + "publish_again_text": "", + }, + } + """Fallback settings to use for `.show_using_settings`, uses v2.10.7 values.""" + @property def settings(self) -> dict[str, Any]: """Return the settings that are available for this hook.""" return self.parent.get_setting("summary").get("settings") or {} - def no_items_error(self, summary_overlay) -> dict[str, Any]: + def show_using_settings(self, key, summary_overlay) -> dict[str, Any]: """Return UI values for the no items collected summary state.""" + settings = self.settings.get(key, {}) + fallback = self.FALLBACK_SETTINGS.get(key, {}) return summary_overlay.show_summary( - ":/tk_multi_publish2/publish_failed.png", - # Hardcoding line break so the message displays on 2 lines. - # Usage of label's own word wrap displays the message below on 3 lines. - # NOTE: Can't manually break line when using

- "Could not find any\nitems to publish.", - "For more details, click here.", + settings.get("icon_path", fallback["icon_path"]), + settings.get("label_text", fallback["label_text"]), + settings.get("info_text", fallback["info_text"]), + publish_again_text=settings.get( + "publish_again_text", fallback["publish_again_text"] + ), ) + def no_items_error(self, summary_overlay) -> dict[str, Any]: + """Return UI values for the no items collected summary state.""" + return self.show_using_settings("no_item_error", summary_overlay) + def success(self, summary_overlay) -> dict[str, Any]: """Return UI values for the publish success summary state.""" - return summary_overlay.show_summary( - ":/tk_multi_publish2/publish_complete.png", - "Publish\nComplete", - "For more details, click here.", - publish_again_text="To publish again, click here.", - ) + return self.show_using_settings("success", summary_overlay) def fail(self, summary_overlay) -> dict[str, Any]: """Return UI values for the publish fail summary state.""" - return summary_overlay.show_summary( - ":/tk_multi_publish2/publish_failed.png", - "Publish\nFailed!", - "For more details, click here.", - ) + return self.show_using_settings("fail", summary_overlay) def loading(self, summary_overlay) -> dict[str, Any]: """Return UI values for the loading summary state.""" - return summary_overlay.show_summary( - ":/tk_multi_publish2/overlay_loading.png", - "Loading and processing", - "Hold tight while we analyze your data", - ) + return self.show_using_settings("loading", summary_overlay) diff --git a/info.yml b/info.yml index 4fe5c0c4..0799580e 100644 --- a/info.yml +++ b/info.yml @@ -38,7 +38,33 @@ configuration: settings: {type: dict, allows_empty: True} default_value: hook: "{self}/summary_hook.py" - settings: {} + settings: + no_item_error: + icon_path: ":/tk_multi_publish2/publish_failed.png" + label_text: | + Could not find any + items to publish. + info_text: "For more details, click here." + publish_again_text: "" + success: + icon_path: ":/tk_multi_publish2/publish_complete.png" + label_text: | + Publish + Complete + info_text: "For more details, click here." + publish_again_text: "To publish again, click here." + fail: + icon_path: ":/tk_multi_publish2/publish_failed.png" + label_text: | + Publish + Failed! + info_text: "For more details, click here." + publish_again_text: "" + loading: + icon_path: ":/tk_multi_publish2/overlay_loading.png" + label_text: "Loading and processing" + info_text: "Hold tight while we analyze your data" + publish_again_text: "" post_phase: type: hook diff --git a/tests/test_summary_hook.py b/tests/test_summary_overlay.py similarity index 53% rename from tests/test_summary_hook.py rename to tests/test_summary_overlay.py index 4adecb9c..0b08d023 100644 --- a/tests/test_summary_hook.py +++ b/tests/test_summary_overlay.py @@ -8,14 +8,16 @@ # agreement to the ShotGrid Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Autodesk. +from contextlib import contextmanager from unittest import mock from publish_api_test_base import PublishApiTestBase from tank_test.tank_test_base import setUpModule # noqa -class TestSummaryHook(PublishApiTestBase): - def check_values_match_v2_10_7(self, summary_method, expected_args): +class TestSummaryOverlay(PublishApiTestBase): + @contextmanager + def check_values_match_v2_10_7(self, expected_args): """Test that calling method sets expected values.""" from sgtk.platform.qt import QtGui @@ -41,56 +43,53 @@ def check_values_match_v2_10_7(self, summary_method, expected_args): expected_args, ) - getattr(self.app.summary_hook, summary_method)(widget) + yield widget + assert widget.show.called assert widget.ui.icon.setPixmap.called for patched_method, expected_arg in patched_methods_expected_args: patched_method.assert_called_with(expected_arg) def test_no_item_error(self): - self.check_values_match_v2_10_7( - "no_items_error", - [ - ":/tk_multi_publish2/publish_failed.png", - "Could not find any\nitems to publish.", - "For more details, click here.", - "", - False, - ], - ) + v2_10_7_values = [ + ":/tk_multi_publish2/publish_failed.png", + "Could not find any\nitems to publish.", + "For more details, click here.", + "", + False, + ] + with self.check_values_match_v2_10_7(v2_10_7_values) as summary_overlay: + summary_overlay.show_no_items_error() def test_success(self): - self.check_values_match_v2_10_7( - "success", - [ - ":/tk_multi_publish2/publish_complete.png", - "Publish\nComplete", - "For more details, click here.", - "To publish again, click here.", - True, - ], - ) + v2_10_7_values = [ + ":/tk_multi_publish2/publish_complete.png", + "Publish\nComplete", + "For more details, click here.", + "To publish again, click here.", + True, + ] + with self.check_values_match_v2_10_7(v2_10_7_values) as summary_overlay: + summary_overlay.show_success() def test_fail(self): - self.check_values_match_v2_10_7( - "fail", - [ - ":/tk_multi_publish2/publish_failed.png", - "Publish\nFailed!", - "For more details, click here.", - "", - False, - ], - ) + v2_10_7_values = [ + ":/tk_multi_publish2/publish_failed.png", + "Publish\nFailed!", + "For more details, click here.", + "", + False, + ] + with self.check_values_match_v2_10_7(v2_10_7_values) as summary_overlay: + summary_overlay.show_fail() def test_loading(self): - self.check_values_match_v2_10_7( - "loading", - [ - ":/tk_multi_publish2/overlay_loading.png", - "Loading and processing", - "Hold tight while we analyze your data", - "", - False, - ], - ) + v2_10_7_values = [ + ":/tk_multi_publish2/overlay_loading.png", + "Loading and processing", + "Hold tight while we analyze your data", + "", + False, + ] + with self.check_values_match_v2_10_7(v2_10_7_values) as summary_overlay: + summary_overlay.show_loading() From 1a497099b02b17cf5ccdb1e92cac5d35b29dee32 Mon Sep 17 00:00:00 2001 From: Joseph Yu Date: Mon, 9 Feb 2026 20:02:57 +0000 Subject: [PATCH 4/5] Less conflict with #212 --- app.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app.py b/app.py index 17b261eb..96f151a2 100644 --- a/app.py +++ b/app.py @@ -83,19 +83,6 @@ def base_hooks(self): """ return self._base_hooks - @property - def summary_hook(self) -> sgtk.Hook: - """Exposes the extensible ``:summary:hook`` instance from app settings. - - Used to fetch information related to summary overlay display content. - """ - self._summary_hook = getattr( - self, - "_summary_hook", - self.create_hook_instance(self.get_setting("summary")["hook"]), - ) - return self._summary_hook - @property def util(self): """ @@ -127,6 +114,19 @@ def context_change_allowed(self): """ return True + @property + def summary_hook(self) -> sgtk.Hook: + """Exposes the extensible ``:summary:hook`` instance from app settings. + + Used to fetch information related to summary overlay display content. + """ + self._summary_hook = getattr( + self, + "_summary_hook", + self.create_hook_instance(self.get_setting("summary")["hook"]), + ) + return self._summary_hook + def create_publish_manager(self, publish_logger=None): """ Create and return a :class:`tk_multi_publish2.PublishManager` instance. From bbafdb05625542e99a47b537d866d42a9ae1469c Mon Sep 17 00:00:00 2001 From: Joseph Yu Date: Wed, 8 Apr 2026 19:48:47 +0100 Subject: [PATCH 5/5] Strip trailing label_text cfg newlines --- info.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/info.yml b/info.yml index 0799580e..2c445274 100644 --- a/info.yml +++ b/info.yml @@ -41,21 +41,21 @@ configuration: settings: no_item_error: icon_path: ":/tk_multi_publish2/publish_failed.png" - label_text: | + label_text: |- Could not find any items to publish. info_text: "For more details, click here." publish_again_text: "" success: icon_path: ":/tk_multi_publish2/publish_complete.png" - label_text: | + label_text: |- Publish Complete info_text: "For more details, click here." publish_again_text: "To publish again, click here." fail: icon_path: ":/tk_multi_publish2/publish_failed.png" - label_text: | + label_text: |- Publish Failed! info_text: "For more details, click here."