Skip to content

Commit

Permalink
Merge pull request #1045 from ynput/enhancement/1035-renders-push-to-…
Browse files Browse the repository at this point in the history
…library-project-error

Push to library project: Error UX improvements
  • Loading branch information
iLLiCiTiT authored Dec 11, 2024
2 parents aee99ca + fa9e53e commit 79903e4
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 24 deletions.
2 changes: 1 addition & 1 deletion client/ayon_core/tools/push_to_project/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ def _check_submit_validations(self):
return False

if (
not self._user_values.new_folder_name
self._user_values.new_folder_name is None
and not self._selection_model.get_selected_folder_id()
):
return False
Expand Down
30 changes: 20 additions & 10 deletions client/ayon_core/tools/push_to_project/models/integrate.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from ayon_core.pipeline.version_start import get_versioning_start
from ayon_core.pipeline.template_data import get_template_data
from ayon_core.pipeline.publish import get_publish_template_name
from ayon_core.pipeline.create import get_product_name
from ayon_core.pipeline.create import get_product_name, TaskNotSetError

UNKNOWN = object()

Expand Down Expand Up @@ -823,15 +823,25 @@ def _determine_product_name(self):
task_name = task_info["name"]
task_type = task_info["taskType"]

product_name = get_product_name(
self._item.dst_project_name,
task_name,
task_type,
self.host_name,
product_type,
self._item.variant,
project_settings=self._project_settings
)
try:
product_name = get_product_name(
self._item.dst_project_name,
task_name,
task_type,
self.host_name,
product_type,
self._item.variant,
project_settings=self._project_settings
)
except TaskNotSetError:
self._status.set_failed(
"Target product name template requires task name. To continue"
" you have to select target task or change settings"
" <b>ayon+settings://core/tools/creator/product_name_profiles"
f"?project={self._item.dst_project_name}</b>."
)
raise PushToProjectError(self._status.fail_reason)

self._log_info(
f"Push will be integrating to product with name '{product_name}'"
)
Expand Down
7 changes: 5 additions & 2 deletions client/ayon_core/tools/push_to_project/models/user_values.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,11 @@ def set_new_folder_name(self, folder_name):
return

self._new_folder_name = folder_name
is_valid = True
if folder_name:
if folder_name is None:
is_valid = True
elif not folder_name:
is_valid = False
else:
is_valid = (
self.folder_name_regex.match(folder_name) is not None
)
Expand Down
135 changes: 124 additions & 11 deletions client/ayon_core/tools/push_to_project/ui/window.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,69 @@
ProjectsCombobox,
FoldersWidget,
TasksWidget,
NiceCheckbox,
)
from ayon_core.tools.push_to_project.control import (
PushToContextController,
)


class ErrorDetailDialog(QtWidgets.QDialog):
def __init__(self, parent):
super().__init__(parent)

self.setWindowTitle("Error detail")
self.setWindowIcon(QtGui.QIcon(get_app_icon_path()))

title_label = QtWidgets.QLabel(self)

sep_1 = SeparatorWidget(parent=self)

detail_widget = QtWidgets.QTextBrowser(self)
detail_widget.setReadOnly(True)
detail_widget.setTextInteractionFlags(
QtCore.Qt.TextBrowserInteraction
)

sep_2 = SeparatorWidget(parent=self)

btns_widget = QtWidgets.QWidget(self)

copy_btn = QtWidgets.QPushButton("Copy", btns_widget)
close_btn = QtWidgets.QPushButton("Close", btns_widget)

btns_layout = QtWidgets.QHBoxLayout(btns_widget)
btns_layout.setContentsMargins(0, 0, 0, 0)
btns_layout.addStretch(1)
btns_layout.addWidget(copy_btn, 0)
btns_layout.addWidget(close_btn, 0)

main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(10, 10, 10, 10)
main_layout.addWidget(title_label, 0)
main_layout.addWidget(sep_1, 0)
main_layout.addWidget(detail_widget, 1)
main_layout.addWidget(sep_2, 0)
main_layout.addWidget(btns_widget, 0)

copy_btn.clicked.connect(self._on_copy_click)
close_btn.clicked.connect(self._on_close_click)

self._title_label = title_label
self._detail_widget = detail_widget

def set_detail(self, title, detail):
self._title_label.setText(title)
self._detail_widget.setText(detail)

def _on_copy_click(self):
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(self._detail_widget.toPlainText())

def _on_close_click(self):
self.close()


class PushToContextSelectWindow(QtWidgets.QWidget):
def __init__(self, controller=None):
super(PushToContextSelectWindow, self).__init__()
Expand Down Expand Up @@ -66,9 +123,12 @@ def __init__(self, controller=None):
# --- Inputs widget ---
inputs_widget = QtWidgets.QWidget(main_splitter)

new_folder_checkbox = NiceCheckbox(True, parent=inputs_widget)

folder_name_input = PlaceholderLineEdit(inputs_widget)
folder_name_input.setPlaceholderText("< Name of new folder >")
folder_name_input.setObjectName("ValidatedLineEdit")
folder_name_input.setEnabled(new_folder_checkbox.isChecked())

variant_input = PlaceholderLineEdit(inputs_widget)
variant_input.setPlaceholderText("< Variant >")
Expand All @@ -79,6 +139,7 @@ def __init__(self, controller=None):

inputs_layout = QtWidgets.QFormLayout(inputs_widget)
inputs_layout.setContentsMargins(0, 0, 0, 0)
inputs_layout.addRow("Create new folder", new_folder_checkbox)
inputs_layout.addRow("New folder name", folder_name_input)
inputs_layout.addRow("Variant", variant_input)
inputs_layout.addRow("Comment", comment_input)
Expand Down Expand Up @@ -113,6 +174,10 @@ def __init__(self, controller=None):

overlay_label = QtWidgets.QLabel(overlay_widget)
overlay_label.setAlignment(QtCore.Qt.AlignCenter)
overlay_label.setWordWrap(True)
overlay_label.setTextInteractionFlags(
QtCore.Qt.TextBrowserInteraction
)

overlay_btns_widget = QtWidgets.QWidget(overlay_widget)
overlay_btns_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
Expand All @@ -121,13 +186,28 @@ def __init__(self, controller=None):
overlay_try_btn = QtWidgets.QPushButton(
"Try again", overlay_btns_widget
)
overlay_try_btn.setToolTip(
"Hide overlay and modify submit information."
)

show_detail_btn = QtWidgets.QPushButton(
"Show error detail", overlay_btns_widget
)
show_detail_btn.setToolTip(
"Show error detail dialog to copy full error."
)

overlay_close_btn = QtWidgets.QPushButton(
"Close", overlay_btns_widget
)
overlay_close_btn.setToolTip("Discard changes and close window.")

overlay_btns_layout = QtWidgets.QHBoxLayout(overlay_btns_widget)
overlay_btns_layout.setContentsMargins(0, 0, 0, 0)
overlay_btns_layout.setSpacing(10)
overlay_btns_layout.addStretch(1)
overlay_btns_layout.addWidget(overlay_try_btn, 0)
overlay_btns_layout.addWidget(show_detail_btn, 0)
overlay_btns_layout.addWidget(overlay_close_btn, 0)
overlay_btns_layout.addStretch(1)

Expand Down Expand Up @@ -156,12 +236,14 @@ def __init__(self, controller=None):
main_thread_timer.timeout.connect(self._on_main_thread_timer)
show_timer.timeout.connect(self._on_show_timer)
user_input_changed_timer.timeout.connect(self._on_user_input_timer)
new_folder_checkbox.stateChanged.connect(self._on_new_folder_check)
folder_name_input.textChanged.connect(self._on_new_folder_change)
variant_input.textChanged.connect(self._on_variant_change)
comment_input.textChanged.connect(self._on_comment_change)

publish_btn.clicked.connect(self._on_select_click)
cancel_btn.clicked.connect(self._on_close_click)
show_detail_btn.clicked.connect(self._on_show_detail_click)
overlay_close_btn.clicked.connect(self._on_close_click)
overlay_try_btn.clicked.connect(self._on_try_again_click)

Expand Down Expand Up @@ -203,23 +285,28 @@ def __init__(self, controller=None):
self._tasks_widget = tasks_widget

self._variant_input = variant_input
self._new_folder_checkbox = new_folder_checkbox
self._folder_name_input = folder_name_input
self._comment_input = comment_input

self._publish_btn = publish_btn

self._overlay_widget = overlay_widget
self._show_detail_btn = show_detail_btn
self._overlay_close_btn = overlay_close_btn
self._overlay_try_btn = overlay_try_btn
self._overlay_label = overlay_label

self._error_detail_dialog = ErrorDetailDialog(self)

self._user_input_changed_timer = user_input_changed_timer
# Store current value on input text change
# The value is unset when is passed to controller
# The goal is to have controll over changes happened during user change
# in UI and controller auto-changes
self._variant_input_text = None
self._new_folder_name_enabled = None
self._new_folder_name_input_text = None
self._variant_input_text = None
self._comment_input_text = None

self._first_show = True
Expand All @@ -235,6 +322,7 @@ def __init__(self, controller=None):
self._folder_is_valid = None

publish_btn.setEnabled(False)
show_detail_btn.setVisible(False)
overlay_close_btn.setVisible(False)
overlay_try_btn.setVisible(False)

Expand Down Expand Up @@ -289,6 +377,11 @@ def _on_show_timer(self):

self.refresh()

def _on_new_folder_check(self):
self._new_folder_name_enabled = self._new_folder_checkbox.isChecked()
self._folder_name_input.setEnabled(self._new_folder_name_enabled)
self._user_input_changed_timer.start()

def _on_new_folder_change(self, text):
self._new_folder_name_input_text = text
self._user_input_changed_timer.start()
Expand All @@ -302,9 +395,15 @@ def _on_comment_change(self, text):
self._user_input_changed_timer.start()

def _on_user_input_timer(self):
folder_name_enabled = self._new_folder_name_enabled
folder_name = self._new_folder_name_input_text
if folder_name is not None:
if folder_name is not None or folder_name_enabled is not None:
self._new_folder_name_input_text = None
self._new_folder_name_enabled = None
if not self._new_folder_checkbox.isChecked():
folder_name = None
elif folder_name is None:
folder_name = self._folder_name_input.text()
self._controller.set_user_value_folder_name(folder_name)

variant = self._variant_input_text
Expand Down Expand Up @@ -350,16 +449,13 @@ def _on_controller_source_change(self):
self._header_label.setText(self._controller.get_source_label())

def _invalidate_new_folder_name(self, folder_name, is_valid):
self._tasks_widget.setVisible(not folder_name)
self._tasks_widget.setVisible(folder_name is None)
if self._folder_is_valid is is_valid:
return
self._folder_is_valid = is_valid
state = ""
if folder_name:
if is_valid is True:
state = "valid"
elif is_valid is False:
state = "invalid"
if folder_name is not None:
state = "valid" if is_valid else "invalid"
set_style_property(
self._folder_name_input, "state", state
)
Expand All @@ -374,6 +470,9 @@ def _invalidate_variant(self, is_valid):
def _on_submission_change(self, event):
self._publish_btn.setEnabled(event["enabled"])

def _on_show_detail_click(self):
self._error_detail_dialog.show()

def _on_close_click(self):
self.close()

Expand All @@ -384,8 +483,11 @@ def _on_try_again_click(self):
self._process_item_id = None
self._last_submit_message = None

self._error_detail_dialog.close()

self._overlay_close_btn.setVisible(False)
self._overlay_try_btn.setVisible(False)
self._show_detail_btn.setVisible(False)
self._main_layout.setCurrentWidget(self._main_context_widget)

def _on_main_thread_timer(self):
Expand All @@ -401,13 +503,24 @@ def _on_main_thread_timer(self):
if self._main_thread_timer_can_stop:
self._main_thread_timer.stop()
self._overlay_close_btn.setVisible(True)
if push_failed and not fail_traceback:
if push_failed:
self._overlay_try_btn.setVisible(True)
if fail_traceback:
self._show_detail_btn.setVisible(True)

if push_failed:
message = "Push Failed:\n{}".format(process_status["fail_reason"])
reason = process_status["fail_reason"]
if fail_traceback:
message += "\n{}".format(fail_traceback)
message = (
"Unhandled error happened."
" Check error detail for more information."
)
self._error_detail_dialog.set_detail(
reason, fail_traceback
)
else:
message = f"Push Failed:\n{reason}"

self._overlay_label.setText(message)
set_style_property(self._overlay_close_btn, "state", "error")

Expand Down

0 comments on commit 79903e4

Please sign in to comment.