Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Push to library project: Error UX improvements #1045

Merged
Merged
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
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
Loading