diff --git a/app.py b/app.py index 18d5f0d6..96f151a2 100644 --- a/app.py +++ b/app.py @@ -114,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. diff --git a/hooks/summary_hook.py b/hooks/summary_hook.py new file mode 100644 index 00000000..51aaf829 --- /dev/null +++ b/hooks/summary_hook.py @@ -0,0 +1,85 @@ +# 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.""" + + 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 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( + 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 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 self.show_using_settings("fail", summary_overlay) + + def loading(self, summary_overlay) -> dict[str, Any]: + """Return UI values for the loading summary state.""" + return self.show_using_settings("loading", summary_overlay) diff --git a/info.yml b/info.yml index 87f8d7e5..2c445274 100644 --- a/info.yml +++ b/info.yml @@ -30,6 +30,42 @@ 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: + 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 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_overlay.py b/tests/test_summary_overlay.py new file mode 100644 index 00000000..0b08d023 --- /dev/null +++ b/tests/test_summary_overlay.py @@ -0,0 +1,95 @@ +# 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 contextlib import contextmanager +from unittest import mock + +from publish_api_test_base import PublishApiTestBase +from tank_test.tank_test_base import setUpModule # noqa + + +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 + + 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, + ) + + 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): + 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): + 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): + 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): + 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()