From 85a7d2e24e3f712eea225cb5baac4ea236cdedbe Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:27:45 +0200 Subject: [PATCH 01/20] added more information about plugin to report --- client/ayon_core/tools/publisher/models/publish.py | 8 +++++++- .../tools/publisher/publish_report_viewer/report_items.py | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/models/publish.py b/client/ayon_core/tools/publisher/models/publish.py index ef207bfb79..42dcca7bb3 100644 --- a/client/ayon_core/tools/publisher/models/publish.py +++ b/client/ayon_core/tools/publisher/models/publish.py @@ -172,7 +172,7 @@ def get_report( "crashed_file_paths": crashed_file_paths, "id": uuid.uuid4().hex, "created_at": now.isoformat(), - "report_version": "1.0.1", + "report_version": "1.1.0", } def _add_plugin_data_item(self, plugin: pyblish.api.Plugin): @@ -194,11 +194,17 @@ def _create_plugin_data_item( if hasattr(plugin, "label"): label = plugin.label + plugin_type = "instance" if plugin.__instanceEnabled__ else "context" + return { "id": plugin.id, "name": plugin.__name__, "label": label, "order": plugin.order, + "filepath": inspect.getfile(plugin), + "docstring": inspect.getdoc(plugin), + "plugin_type": plugin_type, + "families": list(plugin.families), "targets": list(plugin.targets), "instances_data": [], "actions_data": [], diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py b/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py index 206f999bac..cfc2fbfd67 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py @@ -13,6 +13,12 @@ def __init__(self, plugin_data): self.skipped = plugin_data["skipped"] self.passed = plugin_data["passed"] + # Introduced in report '1.1.0' + self.docstring = plugin_data.get("docstring") + self.filepath = plugin_data.get("filepath") + self.plugin_type = plugin_data.get("plugin_type") + self.families = plugin_data.get("families") + errored = False for instance_data in plugin_data["instances_data"]: for log_item in instance_data["logs"]: From d0ea1a31b1562a0803f62fc8c040eb7bb47bf941 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:30:25 +0200 Subject: [PATCH 02/20] mark tab widget as active to avoid long loading time --- .../publish_report_viewer/widgets.py | 103 +++++++++++++++--- 1 file changed, 87 insertions(+), 16 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 61a52533ba..3e2c6adc8a 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -23,32 +23,73 @@ class PluginLoadReportModel(QtGui.QStandardItemModel): - def set_report(self, report): - parent = self.invisibleRootItem() - parent.removeRows(0, parent.rowCount()) + def __init__(self): + super().__init__() + self._traceback_by_filepath = {} + self._items_by_filepath = {} + self._is_active = True + self._need_refresh = False + + def set_active(self, is_active): + if self._is_active is is_active: + return + self._is_active = is_active + self._update_items() + def set_report(self, report): + self._need_refresh = True if report is None: + self._traceback_by_filepath.clear() + self._update_items() + return + + filepaths = set(report.crashed_plugin_paths.keys()) + to_remove = set(self._traceback_by_filepath) - filepaths + for filepath in filepaths: + self._traceback_by_filepath[filepath] = ( + report.crashed_plugin_paths[filepath] + ) + + for filepath in to_remove: + self._traceback_by_filepath.pop(filepath) + self._update_items() + + def _update_items(self): + if not self._is_active or not self._need_refresh: + return + parent = self.invisibleRootItem() + if not self._traceback_by_filepath: + parent.removeRows(0, parent.rowCount()) return new_items = [] new_items_by_filepath = {} - for filepath in report.crashed_plugin_paths.keys(): + to_remove = ( + set(self._items_by_filepath) - set(self._traceback_by_filepath) + ) + for filepath in self._traceback_by_filepath: + if filepath in self._items_by_filepath: + continue item = QtGui.QStandardItem(filepath) new_items.append(item) new_items_by_filepath[filepath] = item + self._items_by_filepath[filepath] = item - if not new_items: - return + if new_items: + parent.appendRows(new_items) - parent.appendRows(new_items) for filepath, item in new_items_by_filepath.items(): - traceback_txt = report.crashed_plugin_paths[filepath] + traceback_txt = self._traceback_by_filepath[filepath] detail_item = QtGui.QStandardItem() detail_item.setData(filepath, FILEPATH_ROLE) detail_item.setData(traceback_txt, TRACEBACK_ROLE) detail_item.setData(True, IS_DETAIL_ITEM_ROLE) item.appendRow(detail_item) + for filepath in to_remove: + item = self._items_by_filepath.pop(filepath) + parent.removeRow(item.row()) + class DetailWidget(QtWidgets.QTextEdit): def __init__(self, text, *args, **kwargs): @@ -95,10 +136,12 @@ def __init__(self, parent): self._model = model self._widgets_by_filepath = {} - def _on_expand(self, index): - for row in range(self._model.rowCount(index)): - child_index = self._model.index(row, index.column(), index) - self._create_widget(child_index) + def set_active(self, is_active): + self._model.set_active(is_active) + + def set_report(self, report): + self._widgets_by_filepath = {} + self._model.set_report(report) def showEvent(self, event): super().showEvent(event) @@ -108,6 +151,11 @@ def resizeEvent(self, event): super().resizeEvent(event) self._update_widgets_size_hints() + def _on_expand(self, index): + for row in range(self._model.rowCount(index)): + child_index = self._model.index(row, index.column(), index) + self._create_widget(child_index) + def _update_widgets_size_hints(self): for item in self._widgets_by_filepath.values(): widget, index = item @@ -136,10 +184,6 @@ def _create_widget(self, index): self._view.setIndexWidget(index, widget) self._widgets_by_filepath[filepath] = (widget, index) - def set_report(self, report): - self._widgets_by_filepath = {} - self._model.set_report(report) - class ZoomPlainText(QtWidgets.QPlainTextEdit): min_point_size = 1.0 @@ -229,6 +273,8 @@ def __init__(self, parent): layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(output_widget) + self._is_active = True + self._need_refresh = False self._output_widget = output_widget self._report_item = None self._instance_filter = set() @@ -237,21 +283,33 @@ def __init__(self, parent): def clear(self): self._output_widget.setPlainText("") + def set_active(self, is_active): + if self._is_active is is_active: + return + self._is_active = is_active + self._update_logs() + def set_report(self, report): self._report_item = report self._plugin_filter = set() self._instance_filter = set() + self._need_refresh = True self._update_logs() def set_plugin_filter(self, plugin_filter): self._plugin_filter = plugin_filter + self._need_refresh = True self._update_logs() def set_instance_filter(self, instance_filter): self._instance_filter = instance_filter + self._need_refresh = True self._update_logs() def _update_logs(self): + if not self._is_active or not self._need_refresh: + return + if not self._report_item: self._output_widget.setPlainText("") return @@ -422,6 +480,8 @@ def __init__(self, parent=None): logs_text_widget = DetailsWidget(details_tab_widget) plugin_load_report_widget = PluginLoadReportWidget(details_tab_widget) + plugin_load_report_widget.set_active(False) + details_tab_widget.addTab(logs_text_widget, "Logs") details_tab_widget.addTab(plugin_load_report_widget, "Crashed plugins") @@ -440,6 +500,7 @@ def __init__(self, parent=None): layout.addWidget(middle_widget, 0) layout.addWidget(details_widget, 1) + details_tab_widget.currentChanged.connect(self._on_tab_change) instances_view.selectionModel().selectionChanged.connect( self._on_instance_change ) @@ -458,6 +519,7 @@ def __init__(self, parent=None): details_popup_btn.clicked.connect(self._on_details_popup) details_popup.closed.connect(self._on_popup_close) + self._current_tab_idx = 0 self._ignore_selection_changes = False self._report_item = None self._logs_text_widget = logs_text_widget @@ -517,6 +579,15 @@ def set_report(self, report): self._instances_view.expandAll() self._plugins_view.expandAll() + def _on_tab_change(self, new_idx): + if self._current_tab_idx == new_idx: + return + old_widget = self._details_tab_widget.widget(self._current_tab_idx) + new_widget = self._details_tab_widget.widget(new_idx) + self._current_tab_idx = new_idx + old_widget.set_active(False) + new_widget.set_active(True) + def _on_instance_change(self, *_args): if self._ignore_selection_changes: return From d073f15a7a8dbd8e9271459e210384c52963898b Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:30:43 +0200 Subject: [PATCH 03/20] added helper 'ElideLabel' widget to elide long text --- client/ayon_core/tools/utils/__init__.py | 2 + client/ayon_core/tools/utils/widgets.py | 64 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/client/ayon_core/tools/utils/__init__.py b/client/ayon_core/tools/utils/__init__.py index 4b5fbeaf67..1eada0c67a 100644 --- a/client/ayon_core/tools/utils/__init__.py +++ b/client/ayon_core/tools/utils/__init__.py @@ -5,6 +5,7 @@ ComboBox, CustomTextComboBox, PlaceholderLineEdit, + ElideLabel, ExpandingTextEdit, BaseClickableFrame, ClickableFrame, @@ -88,6 +89,7 @@ "ComboBox", "CustomTextComboBox", "PlaceholderLineEdit", + "ElideLabel", "ExpandingTextEdit", "BaseClickableFrame", "ClickableFrame", diff --git a/client/ayon_core/tools/utils/widgets.py b/client/ayon_core/tools/utils/widgets.py index 28331fbc35..af85fc915e 100644 --- a/client/ayon_core/tools/utils/widgets.py +++ b/client/ayon_core/tools/utils/widgets.py @@ -104,6 +104,70 @@ def __init__(self, *args, **kwargs): self.setPalette(filter_palette) +class ElideLabel(QtWidgets.QLabel): + """Label which elide text. + + By default, elide happens in middle. Can be changed with + 'set_elide_mode' method. + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.setSizePolicy( + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Preferred + ) + # Store text set during init + self._text = self.text() + # Define initial elide mode + self._elide_mode = QtCore.Qt.ElideMiddle + # Make sure that text of QLabel is empty + super().setText("") + + def setText(self, text): + # Update private text attribute and force update + self._text = text + self.update() + + def setWordWrap(self, word_wrap): + # Word wrap is not supported in 'ElideLabel' + if word_wrap: + raise ValueError("Word wrap is not supported in 'ElideLabel'.") + + def set_set(self, text): + self.setText(text) + + def set_elide_mode(self, elide_mode): + """Change elide type. + + Args: + elide_mode: Possible elide type. Available in 'QtCore.Qt' + 'ElideLeft', 'ElideRight' and 'ElideMiddle'. + + """ + if elide_mode == QtCore.Qt.ElideNone: + raise ValueError( + "Invalid elide type. 'ElideNone' is not supported." + ) + + if elide_mode not in ( + QtCore.Qt.ElideLeft, + QtCore.Qt.ElideRight, + QtCore.Qt.ElideMiddle, + ): + raise ValueError(f"Unknown value '{elide_mode}'") + self._elide_mode = elide_mode + + def paintEvent(self, event): + super().paintEvent(event) + + painter = QtGui.QPainter(self) + fm = painter.fontMetrics() + elided_line = fm.elidedText( + self._text, self._elide_mode, self.width() + ) + painter.drawText(QtCore.QPoint(0, fm.ascent()), elided_line) + + class ExpandingTextEdit(QtWidgets.QTextEdit): """QTextEdit which does not have sroll area but expands height.""" From 2a0208a49406537aad756f7305b1cdc9a235e0eb Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:48:37 +0200 Subject: [PATCH 04/20] added plugin details widget --- .../publish_report_viewer/widgets.py | 161 +++++++++++++++++- 1 file changed, 159 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 3e2c6adc8a..1dc4eccce8 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -1,7 +1,7 @@ from math import ceil from qtpy import QtWidgets, QtCore, QtGui -from ayon_core.tools.utils import NiceCheckbox +from ayon_core.tools.utils import NiceCheckbox, ElideLabel # from ayon_core.tools.utils import DeselectableTreeView from .constants import ( @@ -352,6 +352,155 @@ def _set_logs(self, logs): self._output_widget.setPlainText(text) +class PluginDetailsWidget(QtWidgets.QWidget): + def __init__(self, plugin_item, parent): + super().__init__(parent) + + content_widget = QtWidgets.QWidget(self) + + plugin_label_widget = QtWidgets.QLabel(content_widget) + + plugin_type_label = QtWidgets.QLabel("Plugin type:") + plugin_type_widget = QtWidgets.QLabel(content_widget) + + plugin_path_label = QtWidgets.QLabel("File Path:") + plugin_path_widget = ElideLabel(content_widget) + + plugin_doc_widget = QtWidgets.QLabel(content_widget) + plugin_doc_widget.setWordWrap(True) + + plugin_families_label = QtWidgets.QLabel("Families:") + plugin_families_widget = QtWidgets.QLabel(content_widget) + plugin_families_widget.setWordWrap(True) + + plugin_label_widget.setText(plugin_item.label or plugin_item.name) + plugin_doc_widget.setText(plugin_item.docstring or "N/A") + plugin_type_widget.setText(plugin_item.plugin_type or "N/A") + plugin_path_widget.setText(plugin_item.filepath or "N/A") + plugin_path_widget.setToolTip(plugin_item.filepath or None) + plugin_families_widget.setText(str(plugin_item.families or "N/A")) + + row = 0 + + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setColumnStretch(0, 0) + content_layout.setColumnStretch(1, 1) + + content_layout.addWidget(plugin_label_widget, row, 0, 1, 2) + row += 1 + + content_layout.addWidget(plugin_doc_widget, row, 0, 1, 2) + row += 1 + + content_layout.addWidget(plugin_type_label, row, 0) + content_layout.addWidget(plugin_type_widget, row, 1) + row += 1 + + content_layout.addWidget(plugin_path_label, row, 0) + content_layout.addWidget(plugin_path_widget, row, 1) + row += 1 + + content_layout.addWidget(plugin_families_label, row, 0) + content_layout.addWidget(plugin_families_widget, row, 1) + row += 1 + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.addWidget(content_widget, 0) + + +class PluginsDetailsWidget(QtWidgets.QWidget): + def __init__(self, parent): + super().__init__(parent) + + scroll_area = QtWidgets.QScrollArea(self) + scroll_area.setWidgetResizable(True) + + content_widget = QtWidgets.QWidget(scroll_area) + + scroll_area.setWidget(content_widget) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.addStretch(1) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.addWidget(scroll_area, 1) + + self._scroll_area = scroll_area + self._content_layout = content_layout + self._content_widget = content_widget + + self._widgets_by_plugin_id = {} + + self._is_active = True + self._need_refresh = False + + self._report_item = None + self._plugin_filter = set() + self._plugin_ids = None + + def set_active(self, is_active): + if self._is_active is is_active: + return + self._is_active = is_active + self._update_widgets() + + def set_plugin_filter(self, plugin_filter): + self._plugin_filter = plugin_filter + self._need_refresh = True + self._update_widgets() + + def set_report(self, report): + self._report_item = report + self._plugin_ids = None + self._plugin_filter = set() + self._need_refresh = True + self._update_widgets() + + def _get_plugin_ids(self): + if self._plugin_ids is not None: + return self._plugin_ids + + # Clear layout and clear widgets + while self._content_layout.count(): + self._content_layout.takeAt(0) + + self._widgets_by_plugin_id.clear() + + plugin_ids = [] + if self._report_item is not None: + plugin_ids = list(self._report_item.plugins_id_order) + self._plugin_ids = plugin_ids + return plugin_ids + + def _update_widgets(self): + if not self._is_active or not self._need_refresh: + return + + self._need_refresh = False + + is_new = len(self._widgets_by_plugin_id) == 0 + for plugin_id in self._get_plugin_ids(): + widget = self._widgets_by_plugin_id.get(plugin_id) + if is_new: + plugin_item = self._report_item.plugins_items_by_id[plugin_id] + widget = PluginDetailsWidget(plugin_item, self._content_widget) + self._widgets_by_plugin_id[plugin_id] = widget + + widget.setVisible( + not self._plugin_filter + or plugin_id in self._plugin_filter + ) + + if is_new: + self._content_layout.addWidget(widget, 0) + + if is_new: + self._content_layout.addStretch(1) + + class DeselectableTreeView(QtWidgets.QTreeView): """A tree view that deselects on clicking on an empty area in the view""" @@ -479,11 +628,16 @@ def __init__(self, parent=None): logs_text_widget = DetailsWidget(details_tab_widget) plugin_load_report_widget = PluginLoadReportWidget(details_tab_widget) + plugins_details_widget = PluginsDetailsWidget(details_tab_widget) plugin_load_report_widget.set_active(False) + plugins_details_widget.set_active(False) details_tab_widget.addTab(logs_text_widget, "Logs") - details_tab_widget.addTab(plugin_load_report_widget, "Crashed plugins") + details_tab_widget.addTab(plugins_details_widget, "Plugins Details") + details_tab_widget.addTab( + plugin_load_report_widget, "Crashed plugins" + ) middle_widget = QtWidgets.QWidget(self) middle_layout = QtWidgets.QGridLayout(middle_widget) @@ -524,6 +678,7 @@ def __init__(self, parent=None): self._report_item = None self._logs_text_widget = logs_text_widget self._plugin_load_report_widget = plugin_load_report_widget + self._plugins_details_widget = plugins_details_widget self._removed_instances_check = removed_instances_check self._instances_view = instances_view @@ -573,6 +728,7 @@ def set_report(self, report): self._plugins_model.set_report(report) self._logs_text_widget.set_report(report) self._plugin_load_report_widget.set_report(report) + self._plugins_details_widget.set_report(report) self._ignore_selection_changes = False @@ -609,6 +765,7 @@ def _on_plugin_change(self, *_args): plugin_ids.add(index.data(ITEM_ID_ROLE)) self._logs_text_widget.set_plugin_filter(plugin_ids) + self._plugins_details_widget.set_plugin_filter(plugin_ids) def _on_skipped_plugin_check(self): self._plugins_proxy.set_ignore_skipped( From f65cf0325bc62f53147074c8206a6a71a5fdf5d9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:04:54 +0200 Subject: [PATCH 05/20] overall fixes of details widget --- client/ayon_core/style/style.css | 12 +++ .../publish_report_viewer/widgets.py | 88 ++++++++++++------- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index 607fd1fa31..c8ba2ba8f2 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -1231,6 +1231,18 @@ ValidationArtistMessage QLabel { background: transparent; } +#PluginDetailsContent { + background: {color:bg-inputs}; + border-radius: 0.2em; +} +#PluginDetailsContent #PluginLabel { + font-size: 14pt; + font-weight: bold; +} +#PluginDetailsContent #PluginFormLabel { + font-weight: bold; +} + CreateNextPageOverlay { font-size: 32pt; } diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 1dc4eccce8..263ca0464a 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -356,46 +356,66 @@ class PluginDetailsWidget(QtWidgets.QWidget): def __init__(self, plugin_item, parent): super().__init__(parent) - content_widget = QtWidgets.QWidget(self) + content_widget = QtWidgets.QFrame(self) + content_widget.setObjectName("PluginDetailsContent") plugin_label_widget = QtWidgets.QLabel(content_widget) - - plugin_type_label = QtWidgets.QLabel("Plugin type:") - plugin_type_widget = QtWidgets.QLabel(content_widget) + plugin_label_widget.setObjectName("PluginLabel") + plugin_label_widget.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) plugin_path_label = QtWidgets.QLabel("File Path:") plugin_path_widget = ElideLabel(content_widget) - - plugin_doc_widget = QtWidgets.QLabel(content_widget) - plugin_doc_widget.setWordWrap(True) + plugin_path_widget.set_elide_mode(QtCore.Qt.ElideLeft) plugin_families_label = QtWidgets.QLabel("Families:") plugin_families_widget = QtWidgets.QLabel(content_widget) + plugin_families_widget.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) plugin_families_widget.setWordWrap(True) - plugin_label_widget.setText(plugin_item.label or plugin_item.name) - plugin_doc_widget.setText(plugin_item.docstring or "N/A") - plugin_type_widget.setText(plugin_item.plugin_type or "N/A") + for label_widget in ( + plugin_path_label, + plugin_families_label, + ): + label_widget.setObjectName("PluginFormLabel") + + plugin_doc_widget = QtWidgets.QLabel(content_widget) + plugin_doc_widget.setWordWrap(True) + plugin_doc_widget.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) + + plugin_label = plugin_item.label or plugin_item.name + if plugin_item.plugin_type: + plugin_label += " ({})".format( + plugin_item.plugin_type.capitalize() + ) + plugin_label_widget.setText(plugin_label) + # plugin_type_widget.setText(plugin_item.plugin_type or "N/A") plugin_path_widget.setText(plugin_item.filepath or "N/A") plugin_path_widget.setToolTip(plugin_item.filepath or None) plugin_families_widget.setText(str(plugin_item.families or "N/A")) + plugin_doc_widget.setText(plugin_item.docstring or "N/A") row = 0 content_layout = QtWidgets.QGridLayout(content_widget) - content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setContentsMargins(8, 8, 8, 8) content_layout.setColumnStretch(0, 0) content_layout.setColumnStretch(1, 1) content_layout.addWidget(plugin_label_widget, row, 0, 1, 2) row += 1 - content_layout.addWidget(plugin_doc_widget, row, 0, 1, 2) - row += 1 - - content_layout.addWidget(plugin_type_label, row, 0) - content_layout.addWidget(plugin_type_widget, row, 1) - row += 1 + # Hide docstring if it is empty + if plugin_item.docstring: + content_layout.addWidget(plugin_doc_widget, row, 0, 1, 2) + row += 1 + else: + plugin_doc_widget.setVisible(False) content_layout.addWidget(plugin_path_label, row, 0) content_layout.addWidget(plugin_path_widget, row, 1) @@ -417,12 +437,19 @@ def __init__(self, parent): scroll_area = QtWidgets.QScrollArea(self) scroll_area.setWidgetResizable(True) - content_widget = QtWidgets.QWidget(scroll_area) + scroll_content_widget = QtWidgets.QWidget(scroll_area) - scroll_area.setWidget(content_widget) + scroll_area.setWidget(scroll_content_widget) + + content_widget = QtWidgets.QWidget(scroll_content_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) - content_layout.addStretch(1) + content_layout.setContentsMargins(0, 0, 0, 0) + + scroll_content_layout = QtWidgets.QVBoxLayout(scroll_content_widget) + scroll_content_layout.setContentsMargins(0, 0, 0, 0) + scroll_content_layout.addWidget(content_widget, 0) + scroll_content_layout.addStretch(1) main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) @@ -433,6 +460,7 @@ def __init__(self, parent): self._content_widget = content_widget self._widgets_by_plugin_id = {} + self._stretch_item_index = 0 self._is_active = True self._need_refresh = False @@ -448,15 +476,15 @@ def set_active(self, is_active): self._update_widgets() def set_plugin_filter(self, plugin_filter): - self._plugin_filter = plugin_filter self._need_refresh = True + self._plugin_filter = plugin_filter self._update_widgets() def set_report(self, report): - self._report_item = report self._plugin_ids = None self._plugin_filter = set() self._need_refresh = True + self._report_item = report self._update_widgets() def _get_plugin_ids(self): @@ -465,7 +493,11 @@ def _get_plugin_ids(self): # Clear layout and clear widgets while self._content_layout.count(): - self._content_layout.takeAt(0) + item = self._content_layout.takeAt(0) + widget = item.widget() + if widget: + widget.setVisible(False) + widget.deleteLater() self._widgets_by_plugin_id.clear() @@ -481,25 +513,19 @@ def _update_widgets(self): self._need_refresh = False - is_new = len(self._widgets_by_plugin_id) == 0 for plugin_id in self._get_plugin_ids(): widget = self._widgets_by_plugin_id.get(plugin_id) - if is_new: + if widget is None: plugin_item = self._report_item.plugins_items_by_id[plugin_id] widget = PluginDetailsWidget(plugin_item, self._content_widget) self._widgets_by_plugin_id[plugin_id] = widget + self._content_layout.addWidget(widget, 0) widget.setVisible( not self._plugin_filter or plugin_id in self._plugin_filter ) - if is_new: - self._content_layout.addWidget(widget, 0) - - if is_new: - self._content_layout.addStretch(1) - class DeselectableTreeView(QtWidgets.QTreeView): """A tree view that deselects on clicking on an empty area in the view""" From f16cb1f4e137fa0b20fd18e06472bbf74b2735b7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:05:09 +0200 Subject: [PATCH 06/20] added option to copy text from ElideLabel --- client/ayon_core/tools/utils/widgets.py | 36 +++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/utils/widgets.py b/client/ayon_core/tools/utils/widgets.py index af85fc915e..e58333a07d 100644 --- a/client/ayon_core/tools/utils/widgets.py +++ b/client/ayon_core/tools/utils/widgets.py @@ -107,8 +107,11 @@ def __init__(self, *args, **kwargs): class ElideLabel(QtWidgets.QLabel): """Label which elide text. - By default, elide happens in middle. Can be changed with + By default, elide happens on right side. Can be changed with 'set_elide_mode' method. + + It is not possible to use other features of QLabel like word wrap or + interactive text. This is a simple label which elide text. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -119,7 +122,7 @@ def __init__(self, *args, **kwargs): # Store text set during init self._text = self.text() # Define initial elide mode - self._elide_mode = QtCore.Qt.ElideMiddle + self._elide_mode = QtCore.Qt.ElideRight # Make sure that text of QLabel is empty super().setText("") @@ -133,6 +136,30 @@ def setWordWrap(self, word_wrap): if word_wrap: raise ValueError("Word wrap is not supported in 'ElideLabel'.") + def contextMenuEvent(self, event): + menu = self.create_context_menu(event.pos()) + if menu is None: + event.ignore() + return + event.accept() + menu.setAttribute(QtCore.Qt.WA_DeleteOnClose) + menu.popup(event.globalPos()) + + def create_context_menu(self, pos): + if not self._text: + return None + menu = QtWidgets.QMenu(self) + + # Copy text action + copy_action = menu.addAction("Copy") + copy_action.setObjectName("edit-copy") + icon = QtGui.QIcon.fromTheme("edit-copy") + if not icon.isNull(): + copy_action.setIcon(icon) + + copy_action.triggered.connect(self._on_copy_text) + return menu + def set_set(self, text): self.setText(text) @@ -156,6 +183,7 @@ def set_elide_mode(self, elide_mode): ): raise ValueError(f"Unknown value '{elide_mode}'") self._elide_mode = elide_mode + self.update() def paintEvent(self, event): super().paintEvent(event) @@ -167,6 +195,10 @@ def paintEvent(self, event): ) painter.drawText(QtCore.QPoint(0, fm.ascent()), elided_line) + def _on_copy_text(self): + clipboard = QtWidgets.QApplication.clipboard() + clipboard.setText(self._text) + class ExpandingTextEdit(QtWidgets.QTextEdit): """QTextEdit which does not have sroll area but expands height.""" From 3977a21820d0d6e5d8f547e95a0567b0f8e1450e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:33:59 +0200 Subject: [PATCH 07/20] use only plugin's docstring --- client/ayon_core/tools/publisher/models/publish.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/publisher/models/publish.py b/client/ayon_core/tools/publisher/models/publish.py index 42dcca7bb3..a973d47a11 100644 --- a/client/ayon_core/tools/publisher/models/publish.py +++ b/client/ayon_core/tools/publisher/models/publish.py @@ -195,14 +195,20 @@ def _create_plugin_data_item( label = plugin.label plugin_type = "instance" if plugin.__instanceEnabled__ else "context" - + # Get docstring + # NOTE we do care only about docstring from the plugin so we can't + # use 'inspect.getdoc' which also looks for docstring in parent + # classes. + docstring = getattr(plugin, "__doc__", None) + if docstring: + docstring = inspect.cleandoc(docstring) return { "id": plugin.id, "name": plugin.__name__, "label": label, "order": plugin.order, "filepath": inspect.getfile(plugin), - "docstring": inspect.getdoc(plugin), + "docstring": docstring, "plugin_type": plugin_type, "families": list(plugin.families), "targets": list(plugin.targets), From cf68742129e89f929793c22255a72ee2f84d2105 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:34:20 +0200 Subject: [PATCH 08/20] added class name and order --- .../publish_report_viewer/widgets.py | 74 ++++++++++++------- 1 file changed, 47 insertions(+), 27 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 263ca0464a..a403bd06fc 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -1,7 +1,7 @@ from math import ceil from qtpy import QtWidgets, QtCore, QtGui -from ayon_core.tools.utils import NiceCheckbox, ElideLabel +from ayon_core.tools.utils import NiceCheckbox, ElideLabel, SeparatorWidget # from ayon_core.tools.utils import DeselectableTreeView from .constants import ( @@ -361,51 +361,66 @@ def __init__(self, plugin_item, parent): plugin_label_widget = QtWidgets.QLabel(content_widget) plugin_label_widget.setObjectName("PluginLabel") - plugin_label_widget.setTextInteractionFlags( - QtCore.Qt.TextBrowserInteraction - ) - plugin_path_label = QtWidgets.QLabel("File Path:") - plugin_path_widget = ElideLabel(content_widget) - plugin_path_widget.set_elide_mode(QtCore.Qt.ElideLeft) + plugin_doc_widget = QtWidgets.QLabel(content_widget) + plugin_doc_widget.setWordWrap(True) + + form_separator = SeparatorWidget(parent=content_widget) plugin_families_label = QtWidgets.QLabel("Families:") plugin_families_widget = QtWidgets.QLabel(content_widget) - plugin_families_widget.setTextInteractionFlags( - QtCore.Qt.TextBrowserInteraction - ) plugin_families_widget.setWordWrap(True) + plugin_order_label = QtWidgets.QLabel("Order:") + plugin_order_widget = QtWidgets.QLabel(content_widget) + + plugin_class_label = QtWidgets.QLabel("Class:") + plugin_class_widget = QtWidgets.QLabel(content_widget) + + plugin_path_label = QtWidgets.QLabel("File Path:") + plugin_path_widget = ElideLabel(content_widget) + plugin_path_widget.set_elide_mode(QtCore.Qt.ElideLeft) + + # Set interaction flags + for label_widget in ( + plugin_label_widget, + plugin_families_widget, + plugin_order_widget, + plugin_class_widget, + plugin_doc_widget, + ): + label_widget.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction + ) + + # Change style of form labels for label_widget in ( - plugin_path_label, plugin_families_label, + plugin_order_label, + plugin_class_label, + plugin_path_label, ): label_widget.setObjectName("PluginFormLabel") - plugin_doc_widget = QtWidgets.QLabel(content_widget) - plugin_doc_widget.setWordWrap(True) - plugin_doc_widget.setTextInteractionFlags( - QtCore.Qt.TextBrowserInteraction - ) - plugin_label = plugin_item.label or plugin_item.name if plugin_item.plugin_type: plugin_label += " ({})".format( plugin_item.plugin_type.capitalize() ) plugin_label_widget.setText(plugin_label) - # plugin_type_widget.setText(plugin_item.plugin_type or "N/A") + plugin_doc_widget.setText(plugin_item.docstring or "N/A") + plugin_families_widget.setText(str(plugin_item.families or "N/A")) + plugin_order_widget.setText(str(plugin_item.order or "N/A")) + plugin_class_widget.setText(plugin_item.name or "N/A") plugin_path_widget.setText(plugin_item.filepath or "N/A") + # Show full path in tooltip plugin_path_widget.setToolTip(plugin_item.filepath or None) - plugin_families_widget.setText(str(plugin_item.families or "N/A")) - plugin_doc_widget.setText(plugin_item.docstring or "N/A") - - row = 0 content_layout = QtWidgets.QGridLayout(content_widget) content_layout.setContentsMargins(8, 8, 8, 8) content_layout.setColumnStretch(0, 0) content_layout.setColumnStretch(1, 1) + row = 0 content_layout.addWidget(plugin_label_widget, row, 0, 1, 2) row += 1 @@ -417,13 +432,18 @@ def __init__(self, plugin_item, parent): else: plugin_doc_widget.setVisible(False) - content_layout.addWidget(plugin_path_label, row, 0) - content_layout.addWidget(plugin_path_widget, row, 1) + content_layout.addWidget(form_separator, row, 0, 1, 2) row += 1 - content_layout.addWidget(plugin_families_label, row, 0) - content_layout.addWidget(plugin_families_widget, row, 1) - row += 1 + for label_widget, value_widget in ( + (plugin_families_label, plugin_families_widget), + (plugin_class_label, plugin_class_widget), + (plugin_order_label, plugin_order_widget), + (plugin_path_label, plugin_path_widget), + ): + content_layout.addWidget(label_widget, row, 0) + content_layout.addWidget(value_widget, row, 1) + row += 1 main_layout = QtWidgets.QVBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) From 3bda6370e3049a163b8f642d45d4e71e32b0b51f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:34:29 +0200 Subject: [PATCH 09/20] added spacing to widgets --- .../ayon_core/tools/publisher/publish_report_viewer/widgets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index a403bd06fc..de24e1f653 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -465,6 +465,7 @@ def __init__(self, parent): content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setSpacing(10) scroll_content_layout = QtWidgets.QVBoxLayout(scroll_content_widget) scroll_content_layout.setContentsMargins(0, 0, 0, 0) From 08e0eb03b631dbea795ccc8745a6564ea63ab803 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 17:34:44 +0200 Subject: [PATCH 10/20] remove bold style from labels --- client/ayon_core/style/style.css | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index c8ba2ba8f2..2e3ea53fb3 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -1239,9 +1239,6 @@ ValidationArtistMessage QLabel { font-size: 14pt; font-weight: bold; } -#PluginDetailsContent #PluginFormLabel { - font-weight: bold; -} CreateNextPageOverlay { font-size: 32pt; From c50badd51df6dac2987c46111900f7b07d620b70 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:34:18 +0200 Subject: [PATCH 11/20] added process time --- .../publish_report_viewer/report_items.py | 3 ++ .../publish_report_viewer/widgets.py | 51 ++++++++++++++----- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py b/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py index cfc2fbfd67..a3c5a7a2fd 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/report_items.py @@ -20,7 +20,9 @@ def __init__(self, plugin_data): self.families = plugin_data.get("families") errored = False + process_time = 0.0 for instance_data in plugin_data["instances_data"]: + process_time += instance_data["process_time"] for log_item in instance_data["logs"]: errored = log_item["type"] == "error" if errored: @@ -28,6 +30,7 @@ def __init__(self, plugin_data): if errored: break + self.process_time = process_time self.errored = errored @property diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index de24e1f653..c58fae5c87 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -22,6 +22,21 @@ IS_DETAIL_ITEM_ROLE = QtCore.Qt.UserRole + 3 +def get_pretty_milliseconds(value): + if value < 1000: + return f"{value:.3f}ms" + value /= 1000 + if value < 60: + return f"{value:.2f}s" + seconds = int(value % 60) + value /= 60 + if value < 60: + return f"{value:.2f}m {seconds:.2f}s" + minutes = int(value % 60) + value /= 60 + return f"{value:.2f}h {minutes:.2f}m" + + class PluginLoadReportModel(QtGui.QStandardItemModel): def __init__(self): super().__init__() @@ -367,27 +382,31 @@ def __init__(self, plugin_item, parent): form_separator = SeparatorWidget(parent=content_widget) - plugin_families_label = QtWidgets.QLabel("Families:") - plugin_families_widget = QtWidgets.QLabel(content_widget) - plugin_families_widget.setWordWrap(True) + plugin_class_label = QtWidgets.QLabel("Class:") + plugin_class_widget = QtWidgets.QLabel(content_widget) plugin_order_label = QtWidgets.QLabel("Order:") plugin_order_widget = QtWidgets.QLabel(content_widget) - plugin_class_label = QtWidgets.QLabel("Class:") - plugin_class_widget = QtWidgets.QLabel(content_widget) + plugin_families_label = QtWidgets.QLabel("Families:") + plugin_families_widget = QtWidgets.QLabel(content_widget) + plugin_families_widget.setWordWrap(True) plugin_path_label = QtWidgets.QLabel("File Path:") plugin_path_widget = ElideLabel(content_widget) plugin_path_widget.set_elide_mode(QtCore.Qt.ElideLeft) + plugin_time_label = QtWidgets.QLabel("Time:") + plugin_time_widget = QtWidgets.QLabel(content_widget) + # Set interaction flags for label_widget in ( plugin_label_widget, - plugin_families_widget, - plugin_order_widget, - plugin_class_widget, plugin_doc_widget, + plugin_class_widget, + plugin_order_widget, + plugin_families_widget, + plugin_time_widget, ): label_widget.setTextInteractionFlags( QtCore.Qt.TextBrowserInteraction @@ -395,10 +414,11 @@ def __init__(self, plugin_item, parent): # Change style of form labels for label_widget in ( - plugin_families_label, - plugin_order_label, plugin_class_label, + plugin_order_label, + plugin_families_label, plugin_path_label, + plugin_time_label, ): label_widget.setObjectName("PluginFormLabel") @@ -409,12 +429,14 @@ def __init__(self, plugin_item, parent): ) plugin_label_widget.setText(plugin_label) plugin_doc_widget.setText(plugin_item.docstring or "N/A") - plugin_families_widget.setText(str(plugin_item.families or "N/A")) - plugin_order_widget.setText(str(plugin_item.order or "N/A")) plugin_class_widget.setText(plugin_item.name or "N/A") + plugin_order_widget.setText(str(plugin_item.order or "N/A")) + plugin_families_widget.setText(str(plugin_item.families or "N/A")) plugin_path_widget.setText(plugin_item.filepath or "N/A") - # Show full path in tooltip plugin_path_widget.setToolTip(plugin_item.filepath or None) + plugin_time_widget.setText( + get_pretty_milliseconds(plugin_item.process_time) + ) content_layout = QtWidgets.QGridLayout(content_widget) content_layout.setContentsMargins(8, 8, 8, 8) @@ -436,10 +458,11 @@ def __init__(self, plugin_item, parent): row += 1 for label_widget, value_widget in ( - (plugin_families_label, plugin_families_widget), (plugin_class_label, plugin_class_widget), (plugin_order_label, plugin_order_widget), + (plugin_families_label, plugin_families_widget), (plugin_path_label, plugin_path_widget), + (plugin_time_label, plugin_time_widget), ): content_layout.addWidget(label_widget, row, 0) content_layout.addWidget(value_widget, row, 1) From e54e639b9dd7473d590488fc9506ce3de2f21e45 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:39:23 +0200 Subject: [PATCH 12/20] don't show spent time if did not run yet --- .../tools/publisher/publish_report_viewer/widgets.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 029ce300a8..8227008ff3 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -442,9 +442,14 @@ def __init__(self, plugin_item, parent): plugin_families_widget.setText(str(plugin_item.families or "N/A")) plugin_path_widget.setText(plugin_item.filepath or "N/A") plugin_path_widget.setToolTip(plugin_item.filepath or None) - plugin_time_widget.setText( - get_pretty_milliseconds(plugin_item.process_time) - ) + if plugin_item.passed: + time_label = get_pretty_milliseconds(plugin_item.process_time) + elif plugin_item.skipped: + time_label = "Skipped plugin" + else: + time_label = "Not started" + + plugin_time_widget.setText(time_label) content_layout = QtWidgets.QGridLayout(content_widget) content_layout.setContentsMargins(8, 8, 8, 8) From fd6c6eade49636dbb4604d25c02f235b3711512c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:42:01 +0200 Subject: [PATCH 13/20] details widget is acknowledged about being active --- .../tools/publisher/publish_report_viewer/widgets.py | 8 ++++++++ client/ayon_core/tools/publisher/window.py | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 8227008ff3..af1a7e5281 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -817,6 +817,14 @@ def _on_plugin_view_clicked(self, index): else: self._plugins_view.expand(index) + def set_active(self, active): + for idx in range(self._details_tab_widget.count()): + widget = self._details_tab_widget.widget(idx) + widget.set_active(active and idx == self._current_tab_idx) + + if not active: + self.close_details_popup() + def set_report_data(self, report_data): report = PublishReport(report_data) self.set_report(report) diff --git a/client/ayon_core/tools/publisher/window.py b/client/ayon_core/tools/publisher/window.py index 1218221420..0c6087b41d 100644 --- a/client/ayon_core/tools/publisher/window.py +++ b/client/ayon_core/tools/publisher/window.py @@ -687,13 +687,14 @@ def _on_create_overlay_button_click(self): def _on_tab_change(self, old_tab, new_tab): if old_tab == "details": - self._publish_details_widget.close_details_popup() + self._publish_details_widget.set_active(False) if new_tab == "details": self._content_stacked_layout.setCurrentWidget( self._publish_details_widget ) self._update_publish_details_widget() + self._publish_details_widget.set_active(True) elif new_tab == "report": self._content_stacked_layout.setCurrentWidget( From cf6cf0bf14eba836e575428e476baf99dda38dd1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:54:55 +0200 Subject: [PATCH 14/20] move time label calculation above value changes --- .../publisher/publish_report_viewer/widgets.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index af1a7e5281..b8ddb0ef54 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -435,6 +435,12 @@ def __init__(self, plugin_item, parent): plugin_label += " ({})".format( plugin_item.plugin_type.capitalize() ) + + time_label = "Not started" + if plugin_item.passed: + time_label = get_pretty_milliseconds(plugin_item.process_time) + elif plugin_item.skipped: + time_label = "Skipped plugin" plugin_label_widget.setText(plugin_label) plugin_doc_widget.setText(plugin_item.docstring or "N/A") plugin_class_widget.setText(plugin_item.name or "N/A") @@ -442,13 +448,6 @@ def __init__(self, plugin_item, parent): plugin_families_widget.setText(str(plugin_item.families or "N/A")) plugin_path_widget.setText(plugin_item.filepath or "N/A") plugin_path_widget.setToolTip(plugin_item.filepath or None) - if plugin_item.passed: - time_label = get_pretty_milliseconds(plugin_item.process_time) - elif plugin_item.skipped: - time_label = "Skipped plugin" - else: - time_label = "Not started" - plugin_time_widget.setText(time_label) content_layout = QtWidgets.QGridLayout(content_widget) From f2232f0af15dd6b87323a9d1f5c45fa78f5c7c0c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:55:46 +0200 Subject: [PATCH 15/20] '0' order is not resolved as N/A --- .../tools/publisher/publish_report_viewer/widgets.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index b8ddb0ef54..5e854ae945 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -441,10 +441,14 @@ def __init__(self, plugin_item, parent): time_label = get_pretty_milliseconds(plugin_item.process_time) elif plugin_item.skipped: time_label = "Skipped plugin" + order = plugin_item.order + if order is None: + order = "N/A" + plugin_label_widget.setText(plugin_label) plugin_doc_widget.setText(plugin_item.docstring or "N/A") plugin_class_widget.setText(plugin_item.name or "N/A") - plugin_order_widget.setText(str(plugin_item.order or "N/A")) + plugin_order_widget.setText(order) plugin_families_widget.setText(str(plugin_item.families or "N/A")) plugin_path_widget.setText(plugin_item.filepath or "N/A") plugin_path_widget.setToolTip(plugin_item.filepath or None) From 9fce774a7e545641ab0282e2f71e3e7512d3765d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:56:08 +0200 Subject: [PATCH 16/20] families are joined with comma --- .../tools/publisher/publish_report_viewer/widgets.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 5e854ae945..925547bc1a 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -441,6 +441,11 @@ def __init__(self, plugin_item, parent): time_label = get_pretty_milliseconds(plugin_item.process_time) elif plugin_item.skipped: time_label = "Skipped plugin" + + families = "N/A" + if plugin_item.families: + families = ", ".join(plugin_item.families) + order = plugin_item.order if order is None: order = "N/A" @@ -449,7 +454,7 @@ def __init__(self, plugin_item, parent): plugin_doc_widget.setText(plugin_item.docstring or "N/A") plugin_class_widget.setText(plugin_item.name or "N/A") plugin_order_widget.setText(order) - plugin_families_widget.setText(str(plugin_item.families or "N/A")) + plugin_families_widget.setText(families) plugin_path_widget.setText(plugin_item.filepath or "N/A") plugin_path_widget.setToolTip(plugin_item.filepath or None) plugin_time_widget.setText(time_label) From 4eef7ecadd22185585e4cdb1e7107a69329c455a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:57:18 +0200 Subject: [PATCH 17/20] hide content widget before adding widgets to it's layout --- .../tools/publisher/publish_report_viewer/widgets.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 925547bc1a..6441667264 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -578,6 +578,11 @@ def _update_widgets(self): self._need_refresh = False + # Hide content widget before updating + # - add widgets to layout can happen without recalculating + # the layout and widget size hints + self._content_widget.setVisible(False) + for plugin_id in self._get_plugin_ids(): widget = self._widgets_by_plugin_id.get(plugin_id) if widget is None: @@ -591,6 +596,7 @@ def _update_widgets(self): or plugin_id in self._plugin_filter ) + self._content_widget.setVisible(True) class DeselectableTreeView(QtWidgets.QTreeView): """A tree view that deselects on clicking on an empty area in the view""" From a819797f21a6a1f287fd7dde789eab457a7b7f05 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:58:25 +0200 Subject: [PATCH 18/20] don't show all details if nothing is selected --- .../tools/publisher/publish_report_viewer/widgets.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index 6441667264..df6cd4b168 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -591,10 +591,7 @@ def _update_widgets(self): self._widgets_by_plugin_id[plugin_id] = widget self._content_layout.addWidget(widget, 0) - widget.setVisible( - not self._plugin_filter - or plugin_id in self._plugin_filter - ) + widget.setVisible(plugin_id in self._plugin_filter) self._content_widget.setVisible(True) From 1e7be786eb69f2c0b265b4e7833aa2e5c097a72e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:58:47 +0200 Subject: [PATCH 19/20] added label shown if nothing is selected --- .../publish_report_viewer/widgets.py | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index df6cd4b168..fa4127fe8b 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -505,6 +505,12 @@ def __init__(self, parent): scroll_area.setWidget(scroll_content_widget) + empty_label = QtWidgets.QLabel( + "

Select plugins to view more information...", + scroll_content_widget + ) + empty_label.setAlignment(QtCore.Qt.AlignCenter) + content_widget = QtWidgets.QWidget(scroll_content_widget) content_layout = QtWidgets.QVBoxLayout(content_widget) @@ -513,6 +519,7 @@ def __init__(self, parent): scroll_content_layout = QtWidgets.QVBoxLayout(scroll_content_widget) scroll_content_layout.setContentsMargins(0, 0, 0, 0) + scroll_content_layout.addWidget(empty_label, 0) scroll_content_layout.addWidget(content_widget, 0) scroll_content_layout.addStretch(1) @@ -520,7 +527,10 @@ def __init__(self, parent): main_layout.setContentsMargins(0, 0, 0, 0) main_layout.addWidget(scroll_area, 1) + content_widget.setVisible(False) + self._scroll_area = scroll_area + self._empty_label = empty_label self._content_layout = content_layout self._content_widget = content_widget @@ -583,6 +593,7 @@ def _update_widgets(self): # the layout and widget size hints self._content_widget.setVisible(False) + any_visible = False for plugin_id in self._get_plugin_ids(): widget = self._widgets_by_plugin_id.get(plugin_id) if widget is None: @@ -591,9 +602,14 @@ def _update_widgets(self): self._widgets_by_plugin_id[plugin_id] = widget self._content_layout.addWidget(widget, 0) - widget.setVisible(plugin_id in self._plugin_filter) + is_visible = plugin_id in self._plugin_filter + widget.setVisible(is_visible) + if is_visible: + any_visible = True + + self._content_widget.setVisible(any_visible) + self._empty_label.setVisible(not any_visible) - self._content_widget.setVisible(True) class DeselectableTreeView(QtWidgets.QTreeView): """A tree view that deselects on clicking on an empty area in the view""" From 7e4da0761d558bd42c9f0dcadbdb679d605f1699 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:00:07 +0200 Subject: [PATCH 20/20] order is converted to string --- .../tools/publisher/publish_report_viewer/widgets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py index fa4127fe8b..5fa1c04dc0 100644 --- a/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py +++ b/client/ayon_core/tools/publisher/publish_report_viewer/widgets.py @@ -446,9 +446,9 @@ def __init__(self, plugin_item, parent): if plugin_item.families: families = ", ".join(plugin_item.families) - order = plugin_item.order - if order is None: - order = "N/A" + order = "N/A" + if plugin_item.order is not None: + order = str(plugin_item.order) plugin_label_widget.setText(plugin_label) plugin_doc_widget.setText(plugin_item.docstring or "N/A")