Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
85 changes: 85 additions & 0 deletions hooks/summary_hook.py
Original file line number Diff line number Diff line change
@@ -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 <p></p>
"label_text": "Could not find any\nitems to publish.",
"info_text": "For more details, <b><u>click here</u></b>.",
"publish_again_text": "",
},
"success": {
"icon_path": ":/tk_multi_publish2/publish_complete.png",
"label_text": "Publish\nComplete",
"info_text": "For more details, <b><u>click here</u></b>.",
"publish_again_text": "To publish again, <b><u>click here</u></b>.",
},
"fail": {
"icon_path": ":/tk_multi_publish2/publish_failed.png",
"label_text": "Publish\nFailed!",
"info_text": "For more details, <b><u>click here</u></b>.",
"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)
36 changes: 36 additions & 0 deletions info.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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, <b><u>click here</u></b>."
publish_again_text: ""
success:
icon_path: ":/tk_multi_publish2/publish_complete.png"
label_text: |-
Publish
Complete
info_text: "For more details, <b><u>click here</u></b>."
publish_again_text: "To publish again, <b><u>click here</u></b>."
fail:
icon_path: ":/tk_multi_publish2/publish_failed.png"
label_text: |-
Publish
Failed!
info_text: "For more details, <b><u>click here</u></b>."
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:
Expand Down
1 change: 1 addition & 0 deletions python/tk_multi_publish2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
63 changes: 29 additions & 34 deletions python/tk_multi_publish2/summary_overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 <p></p>
self.ui.label.setText("Could not find any\nitems to publish.")
self.ui.info.setText("For more details, <b><u>click here</u></b>.")
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, <b><u>click here</u></b>.")

self.ui.publish_again.setText("To publish again, <b><u>click here</u></b>.")
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, <b><u>click here</u></b>.")

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):
"""
Expand Down
95 changes: 95 additions & 0 deletions tests/test_summary_overlay.py
Original file line number Diff line number Diff line change
@@ -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, <b><u>click here</u></b>.",
"",
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, <b><u>click here</u></b>.",
"To publish again, <b><u>click here</u></b>.",
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, <b><u>click here</u></b>.",
"",
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()