From 1c4dc0c07e77e6e54c9a8a42e8dabd2fbd61baca Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Sat, 28 Oct 2023 01:04:26 +0100 Subject: [PATCH] widget for upgrading dependencies; line breaks in notifications; formatting --- qt_ui/LogWidget.py | 281 ++++++++++++++++--------- qt_ui/logger.py | 136 ++++++------ qt_ui/ui/dependencies.ui | 99 +++++++++ qt_ui/utils.py | 78 ++++--- qt_ui/widget_add_stream.py | 298 ++++++++++++++++++--------- qt_ui/widget_create_stream.py | 63 ++++-- qt_ui/widget_dependencies_upgrade.py | 98 +++++++++ 7 files changed, 749 insertions(+), 304 deletions(-) create mode 100644 qt_ui/ui/dependencies.ui create mode 100644 qt_ui/widget_dependencies_upgrade.py diff --git a/qt_ui/LogWidget.py b/qt_ui/LogWidget.py index 4b0c28f..cf3f04a 100644 --- a/qt_ui/LogWidget.py +++ b/qt_ui/LogWidget.py @@ -1,4 +1,3 @@ - import inspect from typing import Any, List @@ -11,13 +10,21 @@ from specklepy.logging import metrics from specklepy.core.api.credentials import Account from specklepy_qt_ui.qt_ui.global_resources import ( - BACKGR_COLOR, BACKGR_COLOR_LIGHT, BACKGR_COLOR_GREY, BACKGR_COLOR_TRANSPARENT, BACKGR_COLOR_HIGHLIGHT, - NEW_GREY, NEW_GREY_HIGHLIGHT, BACKGR_ERROR_COLOR, BACKGR_ERROR_COLOR_LIGHT + BACKGR_COLOR, + BACKGR_COLOR_LIGHT, + BACKGR_COLOR_GREY, + BACKGR_COLOR_TRANSPARENT, + BACKGR_COLOR_HIGHLIGHT, + NEW_GREY, + NEW_GREY_HIGHLIGHT, + BACKGR_ERROR_COLOR, + BACKGR_ERROR_COLOR_LIGHT, ) -from specklepy_qt_ui.qt_ui.widget_report import ReportDialog +from specklepy_qt_ui.qt_ui.widget_dependencies_upgrade import DependenciesUpgradeDialog +from specklepy_qt_ui.qt_ui.widget_report import ReportDialog + class LogWidget(QWidget): - dataStorage = None msgs: List[str] = [] used_btns: List[int] = [] @@ -31,54 +38,54 @@ class LogWidget(QWidget): speckle_version: str dockwidget: Any = None reportDialog: Any = None - + # constructor def __init__(self, parent=None): super(LogWidget, self).__init__(parent) self.parentWidget = parent - #print(self.parentWidget) + # print(self.parentWidget) self.max_msg = 8 - - # create a temporary floating button - width = 0 #parent.frameSize().width() - height = 0 # parent.frameSize().height() - + + # create a temporary floating button + width = 0 # parent.frameSize().width() + height = 0 # parent.frameSize().height() + self.setAttribute(QtCore.Qt.WA_StyledBackground, True) self.setStyleSheet("background-color: rgba(250,250,250,80);") - + self.layout = QVBoxLayout(self) self.layout.setContentsMargins(0, 60, 10, 20) - self.layout.setAlignment(Qt.AlignBottom) + self.layout.setAlignment(Qt.AlignBottom) self.setGeometry(0, 0, width, height) self.createBtns() - self.hide() + self.hide() def createBtns(self): # generate 100 buttons to use later self.btns = [] for i in range(self.max_msg): - button = QPushButton(f"👌 Error") # to '{streamName}' Sent , v - #button.setStyleSheet("QPushButton {color: black; border: 0px;border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR_GREY}" + "}") + button = QPushButton(f"👌 Error") # to '{streamName}' Sent , v + # button.setStyleSheet("QPushButton {color: black; border: 0px;border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR_GREY}" + "}") button.clicked.connect(lambda: self.btnClicked()) self.btns.append(button) # overriding the mouseReleaseEvent method def mouseReleaseEvent(self, event): - #print("Mouse Release Event") - self.hide() - #self.parentWidget.hideError() + # print("Mouse Release Event") + self.hide() + # self.parentWidget.hideError() def hide(self): - #print("___HIDE LOG WIDGET") - + # print("___HIDE LOG WIDGET") + self.setGeometry(0, 0, 0, 0) # remove all buttons - for i in reversed(range(self.layout.count())): - #print(self.layout.itemAt(i)) - #print(self.layout.itemAt(i).widget()) - self.layout.itemAt(i).widget().setParent(None) + for i in reversed(range(self.layout.count())): + # print(self.layout.itemAt(i)) + # print(self.layout.itemAt(i).widget()) + self.layout.itemAt(i).widget().setParent(None) self.createBtns() # remove list of used btns @@ -86,21 +93,25 @@ class LogWidget(QWidget): self.msgs.clear() def addButton(self, obj: dict): - #print("Add button") + # print("Add button") text: str = obj["text"] level: int = obj["level"] - url: str = obj["url"] + url: str = obj["url"] blue: bool = obj["blue"] report: bool = obj["report"] + self.setGeometry( + 0, + 0, + self.parentWidget.frameSize().width(), + self.parentWidget.frameSize().height(), + ) - self.setGeometry(0, 0, self.parentWidget.frameSize().width(), self.parentWidget.frameSize().height()) - # find index of the first unused button btn, index = self.getNextBtn() - #print(btn) + # print(btn) btn.setAccessibleName(url) - #print(btn) + # print(btn) btn.setText(text) self.resizeToText(btn) @@ -108,45 +119,79 @@ class LogWidget(QWidget): boxLayout = QHBoxLayout(widget) spacer = QPushButton("") - spacer.setStyleSheet("QPushButton {padding:0px;"+ f"{BACKGR_COLOR_TRANSPARENT}" + "}") + spacer.setStyleSheet( + "QPushButton {padding:0px;" + f"{BACKGR_COLOR_TRANSPARENT}" + "}" + ) spacer.setMaximumWidth(10) - - # add btns to widget layout - boxLayout.addWidget(btn) #, alignment=Qt.AlignCenter) - - # add report - reportBtn = QPushButton(f"☑️ Report") # 📈 to '{streamName}' Sent , v + + # add btns to widget layout + boxLayout.addWidget(btn) # , alignment=Qt.AlignCenter) + + # add report + reportBtn = QPushButton(f"☑️ Report") # 📈 to '{streamName}' Sent , v reportBtn.clicked.connect(lambda: self.showReport()) reportBtn.setMaximumWidth(150) - reportBtn.setStyleSheet("QPushButton {color: white; border-radius: 17px;padding:0px;padding-left: 10px;padding-right: 10px;text-align: center;"+ f"{NEW_GREY}" + "} QPushButton:hover { "+ f"{NEW_GREY_HIGHLIGHT}" + " }") + reportBtn.setStyleSheet( + "QPushButton {color: white; border-radius: 17px;padding:0px;padding-left: 10px;padding-right: 10px;text-align: center;" + + f"{NEW_GREY}" + + "} QPushButton:hover { " + + f"{NEW_GREY_HIGHLIGHT}" + + " }" + ) - if report is True: - # color report btn + if report is True: + # color report btn reportList = self.dataStorage.latestActionReport - if reportList is not None: + if reportList is not None: for item in reportList: if item["errors"] != "": reportBtn.setText("⚠️ Report") - #reportBtn.setStyleSheet("QPushButton {color: white; border-radius: 17px;padding:0px;padding-left: 10px;padding-right: 10px;text-align: center;"+ f"{BACKGR_ERROR_COLOR}" + "} QPushButton:hover { "+ f"{BACKGR_ERROR_COLOR_LIGHT}" + " }") - break + # reportBtn.setStyleSheet("QPushButton {color: white; border-radius: 17px;padding:0px;padding-left: 10px;padding-right: 10px;text-align: center;"+ f"{BACKGR_ERROR_COLOR}" + "} QPushButton:hover { "+ f"{BACKGR_ERROR_COLOR_LIGHT}" + " }") + break boxLayout.addWidget(reportBtn) boxLayout.addWidget(spacer) if url != "": - widget.setStyleSheet("QWidget {border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR}" + "} QWidget:hover { "+ f"{BACKGR_COLOR_LIGHT}" + " }") - btn.setStyleSheet("QPushButton {color: white;border: 0px; padding:0px; padding-left: 10px;text-align: left;"+ f"{BACKGR_COLOR_TRANSPARENT}" + "}") - else: # without url - if blue is False: - widget.setStyleSheet("QWidget {border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR_GREY}" + "}") - btn.setStyleSheet("QPushButton {color: black; border: 0px; padding:0px; padding-left: 10px;text-align: left;"+ f"{BACKGR_COLOR_TRANSPARENT}" + "}") - else: # blue, no URL (after receive) - widget.setStyleSheet("QWidget {border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR}" + "}") - btn.setStyleSheet("QPushButton {color: white;border: 0px; padding:0px; padding-left: 10px;text-align: left;"+ f"{BACKGR_COLOR_TRANSPARENT}" + "}") - - self.reportBtn = reportBtn + widget.setStyleSheet( + "QWidget {border-radius: 17px;padding: 20px;height: 40px;text-align: left;" + + f"{BACKGR_COLOR}" + + "} QWidget:hover { " + + f"{BACKGR_COLOR_LIGHT}" + + " }" + ) + btn.setStyleSheet( + "QPushButton {color: white;border: 0px; padding:0px; padding-left: 10px;text-align: left;" + + f"{BACKGR_COLOR_TRANSPARENT}" + + "}" + ) + else: # without url + if blue is False: + widget.setStyleSheet( + "QWidget {border-radius: 17px;padding: 20px;height: 40px;text-align: left;" + + f"{BACKGR_COLOR_GREY}" + + "}" + ) + btn.setStyleSheet( + "QPushButton {color: black; border: 0px; padding:0px; padding-left: 10px;text-align: left;" + + f"{BACKGR_COLOR_TRANSPARENT}" + + "}" + ) + else: # blue, no URL (after receive) + widget.setStyleSheet( + "QWidget {border-radius: 17px;padding: 20px;height: 40px;text-align: left;" + + f"{BACKGR_COLOR}" + + "}" + ) + btn.setStyleSheet( + "QPushButton {color: white;border: 0px; padding:0px; padding-left: 10px;text-align: left;" + + f"{BACKGR_COLOR_TRANSPARENT}" + + "}" + ) - self.layout.addWidget(widget) #, alignment=Qt.AlignCenter) + self.reportBtn = reportBtn + + self.layout.addWidget(widget) # , alignment=Qt.AlignCenter) self.msgs.append(text) self.used_btns.append(1) @@ -156,91 +201,129 @@ class LogWidget(QWidget): self.reportDialog.applyReport() self.reportDialog.show() return - + def openURL(self, url: str = ""): try: - metrics.track("Connector Action", self.dataStorage.active_account, {"name": "Open In Web", "connector_version": str(self.speckle_version)}) + metrics.track( + "Connector Action", + self.dataStorage.active_account, + {"name": "Open In Web", "connector_version": str(self.speckle_version)}, + ) except Exception as e: - print(e) - print(inspect.stack()[0][3]) - + print(e) + print(inspect.stack()[0][3]) + if url is not None and url != "": webbrowser.open(url, new=0, autoraise=True) - - def btnClicked(self, url = ""): + def btnClicked(self, url=""): try: btn = self.sender() url = btn.accessibleName() - if url == "" or not isinstance(url, str): + if url == "" or not isinstance(url, str): return - elif isinstance(url, str): - if url.startswith("http"): - self.openURL(url) + elif isinstance(url, str): + if url.startswith("http"): + self.openURL(url) + elif url.startswith("dependencies"): + try: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Details on resolving dependencies", + "connector_version": str( + self.dataStorage.plugin_version + ), + }, + ) + except Exception as e: + print(e) + self.dependenciesDialog = DependenciesUpgradeDialog() + self.dependenciesDialog.show() + elif url.startswith("cancel"): - - try: metrics.track("Connector Action", self.dataStorage.active_account, {"name": "Cancel Operation", "connector_version": str(self.dataStorage.plugin_version)}) - except Exception as e: print(e) + try: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Cancel Operation", + "connector_version": str( + self.dataStorage.plugin_version + ), + }, + ) + except Exception as e: + print(e) self.hide() - + self.parentWidget.cancelOperations() - except Exception as e: + except Exception as e: print(e) - pass #logger.logToUser(str(e), level=2, func = inspect.stack()[0][3]) - #self.hide() + pass # logger.logToUser(str(e), level=2, func = inspect.stack()[0][3]) + # self.hide() def getNextBtn(self): - index = len(self.used_btns) # get the next "free" button - #print(index) - #print(self.btns) + index = len(self.used_btns) # get the next "free" button + # print(index) + # print(self.btns) - if index >= len(self.btns): + if index >= len(self.btns): # remove first button print(self.layout.itemAt(0).widget()) self.layout.itemAt(0).widget().setParent(None) - #self.used_btns.clear() + # self.used_btns.clear() self.createBtns() - index = 0 + index = 0 btn = self.btns[index] - #print(btn) - return btn, index - + # print(btn) + return btn, index + def getBtnByKeyword(self, keyword: str): try: new_btn = None for btn in self.btns: - url = btn.accessibleName() - if keyword in url: - new_btn = btn + url = btn.accessibleName() + if keyword in url: + new_btn = btn break return new_btn except Exception as e: print(e) - + def removeBtnUrl(self, keyword: str = "cancel"): try: btn = self.getBtnByKeyword(keyword) if btn is not None: btn.setAccessibleName("") - btn.setStyleSheet("QPushButton {color: black; border: 0px; padding-left: 10px;text-align: left;"+ f"{BACKGR_COLOR_TRANSPARENT}" + "}") - #widget.setStyleSheet("QWidget {border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR_GREY}" + "}") - #btn.setStyleSheet("QPushButton {color: black}") - btn.parent().setStyleSheet("QWidget {border-radius: 17px;padding-left: 10px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR_GREY}" + "}") - + btn.setStyleSheet( + "QPushButton {color: black; border: 0px; padding-left: 10px;text-align: left;" + + f"{BACKGR_COLOR_TRANSPARENT}" + + "}" + ) + # widget.setStyleSheet("QWidget {border-radius: 17px;padding: 20px;height: 40px;text-align: left;"+ f"{BACKGR_COLOR_GREY}" + "}") + # btn.setStyleSheet("QPushButton {color: black}") + btn.parent().setStyleSheet( + "QWidget {border-radius: 17px;padding-left: 10px;height: 40px;text-align: left;" + + f"{BACKGR_COLOR_GREY}" + + "}" + ) + except Exception as e: print(e) - + def resizeToText(self, btn): try: text = btn.text() - #if len(text.split("\n"))>2: - height = len(text.split("\n"))*25 + 20 + # if len(text.split("\n"))>2: + height = len(text.split("\n")) * 25 + 20 btn.setMinimumHeight(height) - return btn - except Exception as e: + return btn + except Exception as e: print(e) - return btn \ No newline at end of file + return btn diff --git a/qt_ui/logger.py b/qt_ui/logger.py index 9351805..3279965 100644 --- a/qt_ui/logger.py +++ b/qt_ui/logger.py @@ -1,74 +1,88 @@ - from PyQt5.QtWidgets import QMessageBox from PyQt5 import QtCore from specklepy_qt_ui.qt_ui.utils import splitTextIntoLines -def logToUser(msg: str, func=None, level: int = 2, plugin = None, url = "", blue = False, report = False): - #print("Log to user") - msg = str(msg) - #if func is not None and func != "None": - # print(msg + " " + url + "::" + str(func)) - #else: - # print(msg + " " + url ) - dockwidget = plugin - try: - if url == "" and blue is False: # only for info messages - msg = addLevelSymbol(msg, level) - if func is not None: - msg += "::" + str(func) - if dockwidget is None: return - - new_msg = splitTextIntoLines(msg) - dockwidget.msgLog.sendMessage.emit({"text":new_msg, "level":level, "url":url, "blue":blue, "report":report}) - - except Exception as e: print(e); return +def logToUser( + msg: str, func=None, level: int = 2, plugin=None, url="", blue=False, report=False +): + msg = str(msg) + dockwidget = plugin + try: + if url == "" and blue is False: # only for info messages + msg = addLevelSymbol(msg, level) + if func is not None: + msg += "::" + str(func) + if dockwidget is None: + return + + new_msg = splitTextIntoLines(msg) + dockwidget.msgLog.sendMessage.emit( + { + "text": new_msg, + "level": level, + "url": url, + "blue": blue, + "report": report, + } + ) + + except Exception as e: + print(e) + return + def addLevelSymbol(msg: str, level: int): - if level == 0: msg = "🛈 " + msg - if level == 1: msg = "⚠️ " + msg - if level == 2: msg = "❗ " + msg - return msg + if level == 0: + msg = "🛈 " + msg + if level == 1: + msg = "⚠️ " + msg + if level == 2: + msg = "❗ " + msg + return msg + + +def displayUserMsg(msg: str, func=None, level: int = 2): + try: + window = createWindow(msg, func, level) + window.exec_() + except Exception as e: + print(e) -def displayUserMsg(msg: str, func=None, level: int = 2): - try: - window = createWindow(msg, func, level) - window.exec_() - except Exception as e: print(e) def createWindow(msg_old: str, func=None, level: int = 2): - #print("Create window") - window = None - try: - # https://www.techwithtim.net/tutorials/pyqt5-tutorial/messageboxes/ - window = QMessageBox() - msg = "" - if len(msg_old)>80: - for line in msg_old.split("\n"): - line = splitTextIntoLines(line) - msg += line + "\n" - else: - msg = msg_old - - window.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint) - if level==0: - window.setWindowTitle("Info (Speckle)") - window.setIcon(QMessageBox.Icon.Information) - if level==1: - window.setWindowTitle("Warning (Speckle)") - window.setIcon(QMessageBox.Icon.Warning) - elif level==2: - window.setWindowTitle("Error (Speckle)") - window.setIcon(QMessageBox.Icon.Critical) - window.setFixedWidth(200) - #window.setTextFormat(QtCore.Qt.RichText) + # print("Create window") + window = None + try: + # https://www.techwithtim.net/tutorials/pyqt5-tutorial/messageboxes/ + window = QMessageBox() + msg = "" + if len(msg_old) > 80: + for line in msg_old.split("\n"): + line = splitTextIntoLines(line) + msg += line + "\n" + else: + msg = msg_old - if func is not None: - window.setText(str(msg + "\n" + str(func))) - else: - window.setText(str(msg)) - #print(window) - except Exception as e: print(e) - return window + window.setWindowFlag(QtCore.Qt.WindowStaysOnTopHint) + if level == 0: + window.setWindowTitle("Info (Speckle)") + window.setIcon(QMessageBox.Icon.Information) + if level == 1: + window.setWindowTitle("Warning (Speckle)") + window.setIcon(QMessageBox.Icon.Warning) + elif level == 2: + window.setWindowTitle("Error (Speckle)") + window.setIcon(QMessageBox.Icon.Critical) + window.setFixedWidth(200) + # window.setTextFormat(QtCore.Qt.RichText) + if func is not None: + window.setText(str(msg + "\n" + str(func))) + else: + window.setText(str(msg)) + # print(window) + except Exception as e: + print(e) + return window diff --git a/qt_ui/ui/dependencies.ui b/qt_ui/ui/dependencies.ui new file mode 100644 index 0000000..409f10b --- /dev/null +++ b/qt_ui/ui/dependencies.ui @@ -0,0 +1,99 @@ + + + DependenciesUpgradeDialog + + + + + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + + + + + + + + + + + + + + Option: + + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + + + + + + + + + Upgrade dependencies automatically + + + + + + + Cancel + + + + + + + + + + + + + + + + diff --git a/qt_ui/utils.py b/qt_ui/utils.py index 88ce464..14ccd34 100644 --- a/qt_ui/utils.py +++ b/qt_ui/utils.py @@ -1,59 +1,77 @@ - from textwrap import wrap -def splitTextIntoLines(text: str = "", number: int= 40) -> str: - #print("__splitTextIntoLines") - #print(text) +def splitTextIntoLines(text: str = "", number: int = 40) -> str: + # print("__splitTextIntoLines") + # print(text) msg = "" try: - if len(text)>number: + if len(text) > number: try: - lines = wrap(text, number) - for i, x in enumerate(lines): - msg += x - if i!= len(lines) - 1: + for i, text_part in enumerate(text.split("\n")): + lines = wrap(text_part, number) + for k, x in enumerate(lines): + msg += x + if k != len(lines) - 1: + msg += "\n" + if i != len(text.split("\n")) - 1: msg += "\n" - except Exception as e: print(e) - else: + except Exception as e: + print(e) + else: msg = text except Exception as e: print(e) print(text) return msg -def constructCommitURL(streamWrapper, branch_id: str = None, commit_id: str = None) -> str: - import requests + +def constructCommitURL( + streamWrapper, branch_id: str = None, commit_id: str = None +) -> str: + import requests + try: if isinstance(streamWrapper, tuple) or isinstance(streamWrapper, list): streamWrapper = streamWrapper[0] streamUrl = streamWrapper.stream_url.split("?")[0].split("&")[0].split("@")[0] r = requests.get(streamUrl) - - url = streamUrl - # check for frontend2 - try: - header = r.headers['x-speckle-frontend-2'] - url = streamUrl.replace("streams", "projects") + "/models/" + branch_id + "@" + commit_id + + url = streamUrl + # check for frontend2 + try: + header = r.headers["x-speckle-frontend-2"] + url = ( + streamUrl.replace("streams", "projects") + + "/models/" + + branch_id + + "@" + + commit_id + ) except: url = streamUrl.replace("projects", "streams") + "/commits/" + commit_id - return url + return url except: - pass + pass + def constructCommitURLfromServerCommit(serverURL: str, stream_id: str) -> str: - import requests + import requests + r = requests.get(serverURL) - - # check for frontend2 - try: - header = r.headers['x-speckle-frontend-2'] - #url = streamUrl.replace("streams", "projects") + "/models/" + branch_id + "@" + commit_id - url = serverURL + "/projects/" + stream_id # replace with 'projects' after it's implemented in Specklepy + + # check for frontend2 + try: + header = r.headers["x-speckle-frontend-2"] + # url = streamUrl.replace("streams", "projects") + "/models/" + branch_id + "@" + commit_id + url = ( + serverURL + "/projects/" + stream_id + ) # replace with 'projects' after it's implemented in Specklepy except: url = serverURL + "/streams/" + stream_id - return url + return url -#def removeSpecialCharacters(text: str) -> str: + +# def removeSpecialCharacters(text: str) -> str: # new_text = text.replace("[","_").replace("]","_").replace(" ","_").replace("-","_").replace("(","_").replace(")","_").replace(":","_").replace("\\","_").replace("/","_").replace("\"","_").replace("&","_").replace("@","_").replace("$","_").replace("%","_").replace("^","_") # return new_text diff --git a/qt_ui/widget_add_stream.py b/qt_ui/widget_add_stream.py index f3f6d9a..43f8848 100644 --- a/qt_ui/widget_add_stream.py +++ b/qt_ui/widget_add_stream.py @@ -12,7 +12,7 @@ from PyQt5.QtCore import pyqtSignal from specklepy.core.api.models import Stream, Branch, Commit from specklepy.core.api.client import SpeckleClient from specklepy.logging.exceptions import SpeckleException -from specklepy.core.api.credentials import get_local_accounts #, StreamWrapper +from specklepy.core.api.credentials import get_local_accounts # , StreamWrapper from specklepy.core.api.wrapper import StreamWrapper from specklepy.logging import metrics @@ -20,11 +20,11 @@ from specklepy_qt_ui.qt_ui.utils import constructCommitURLfromServerCommit # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer FORM_CLASS, _ = uic.loadUiType( - os.path.join(os.path.join(os.path.dirname(__file__), "ui", "add_stream_modal.ui") ) + os.path.join(os.path.join(os.path.dirname(__file__), "ui", "add_stream_modal.ui")) ) -class AddStreamModalDialog(QtWidgets.QWidget, FORM_CLASS): +class AddStreamModalDialog(QtWidgets.QWidget, FORM_CLASS): search_button: QtWidgets.QPushButton = None search_text_field: QtWidgets.QLineEdit = None search_results_list: QtWidgets.QListWidget = None @@ -35,192 +35,305 @@ class AddStreamModalDialog(QtWidgets.QWidget, FORM_CLASS): branch_result: Branch = None commit_result: Commit = None speckle_client: Union[SpeckleClient, None] = None - dataStorage: DataStorage = None + dataStorage: DataStorage = None - #Events + # Events handleStreamAdd = pyqtSignal(object) def __init__(self, parent=None, speckle_client: SpeckleClient = None): - super(AddStreamModalDialog,self).__init__(parent,QtCore.Qt.WindowStaysOnTopHint) + super(AddStreamModalDialog, self).__init__( + parent, QtCore.Qt.WindowStaysOnTopHint + ) self.speckle_client = speckle_client self.setupUi(self) self.setWindowTitle("Add Speckle stream") - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False) + self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False) def connect(self): self.search_button.clicked.connect(self.onSearchClicked) - self.search_results_list.currentItemChanged.connect( self.searchResultChanged ) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onOkClicked) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.onCancelClicked) + self.search_results_list.currentItemChanged.connect(self.searchResultChanged) + self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect( + self.onOkClicked + ) + self.dialog_button_box.button( + QtWidgets.QDialogButtonBox.Cancel + ).clicked.connect(self.onCancelClicked) self.accounts_dropdown.currentIndexChanged.connect(self.onAccountSelected) self.populate_accounts_dropdown() def searchResultChanged(self): try: index = self.search_results_list.currentIndex().row() - if index == -1: self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False) - else: self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True) + if index == -1: + self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled( + False + ) + else: + self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled( + True + ) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return def getAllStreams(self): try: query = "" - sw = None + sw = None results = [] - if self.speckle_client is not None: + if self.speckle_client is not None: results = self.speckle_client.stream.search(query) - try: metrics.track("Connector Action", self.dataStorage.active_account, {"name": "Stream Search By Name", "connector_version": str(self.dataStorage.plugin_version)}) - except Exception as e: logToUser(e, level = 2, func = inspect.stack()[0][3] ) - - elif self.speckle_client is None: - logToUser(f"Account cannot be authenticated: {self.accounts_dropdown.currentText()}", level = 1, func = inspect.stack()[0][3]) - + try: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Stream Search By Name", + "connector_version": str(self.dataStorage.plugin_version), + }, + ) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + + elif self.speckle_client is None: + logToUser( + f"Account cannot be authenticated: {self.accounts_dropdown.currentText()}", + level=1, + func=inspect.stack()[0][3], + ) + self.stream_results = results self.populateResultsList() except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return - + def onSearchClicked(self): try: query = self.search_text_field.text() - sw = None + sw = None streams = [] branch = None commit = None - #print("_____ onSearchClicked___") - if "http" in query and len(query.split("/")) >= 3: # URL + # print("_____ onSearchClicked___") + if "http" in query and len(query.split("/")) >= 3: # URL sw = StreamWrapper(query) - stream = sw.get_client().stream.get(id = sw.stream_id, branch_limit = 100, commit_limit = 100) - if isinstance(stream, Stream): + stream = sw.get_client().stream.get( + id=sw.stream_id, branch_limit=100, commit_limit=100 + ) + if isinstance(stream, Stream): streams = [stream] if "/branches/" in query: - #print("branches") - branch_name = query.split("/branches/")[len(query.split("/branches/"))-1].split("/")[0].split("?")[0].split("&")[0].split("@")[0] - #print(branch_name) - #print(stream) - #print(len(stream.branches.items)) + # print("branches") + branch_name = ( + query.split("/branches/")[ + len(query.split("/branches/")) - 1 + ] + .split("/")[0] + .split("?")[0] + .split("&")[0] + .split("@")[0] + ) + # print(branch_name) + # print(stream) + # print(len(stream.branches.items)) for br in stream.branches.items: name = urllib.parse.quote(br.name) - #print(name) + # print(name) if name == branch_name: branch = br - break + break elif "/commits/" in query: - #print("commits") - commit_id = query.split("/commits/")[len(query.split("/commits/"))-1].split("/")[0].split("?")[0].split("&")[0].split("@")[0] + # print("commits") + commit_id = ( + query.split("/commits/")[len(query.split("/commits/")) - 1] + .split("/")[0] + .split("?")[0] + .split("&")[0] + .split("@")[0] + ) for br in stream.branches.items: for com in br.commits.items: if com.id == commit_id: branch = br - commit = com - #print(branch) - #print(commit) + commit = com + # print(branch) + # print(commit) break elif isinstance(stream, Exception): print(stream) - #if "/commits/" in query: + # if "/commits/" in query: # branch_id = query.split("/commits/")[len(query.split("/commits/"))-1].split("/")[0].split("?")[0].split("&")[0].split("@")[0] # for com in stream. # if br.id == branch_id: # branch = br - # break - - try: metrics.track("Connector Action", self.dataStorage.active_account, {"name": "Stream Search By URL", "connector_version": str(self.dataStorage.plugin_version)}) - except Exception as e: logToUser(e, level = 2, func = inspect.stack()[0][3] ) - - elif self.speckle_client is not None: + # break + + try: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Stream Search By URL", + "connector_version": str(self.dataStorage.plugin_version), + }, + ) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + + elif self.speckle_client is not None: streams = self.speckle_client.stream.search(query) - try: metrics.track("Connector Action", self.dataStorage.active_account, {"name": "Stream Search By Name", "connector_version": str(self.dataStorage.plugin_version)}) - except Exception as e: logToUser(e, level = 2, func = inspect.stack()[0][3] ) - - elif self.speckle_client is None: - logToUser(f"Account cannot be authenticated: {self.accounts_dropdown.currentText()}", level = 1, func = inspect.stack()[0][3]) - + try: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Stream Search By Name", + "connector_version": str(self.dataStorage.plugin_version), + }, + ) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + + elif self.speckle_client is None: + logToUser( + f"Account cannot be authenticated: {self.accounts_dropdown.currentText()}", + level=1, + func=inspect.stack()[0][3], + ) + self.stream_results = streams self.branch_result = branch self.commit_result = commit self.populateResultsList(sw) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return - - def populateResultsList(self, sw = None): + + def populateResultsList(self, sw=None): try: self.search_results_list.clear() - if isinstance(self.stream_results, SpeckleException): - logToUser("Some streams cannot be accessed", level = 1, func = inspect.stack()[0][3]) - return + if isinstance(self.stream_results, SpeckleException): + logToUser( + "Some streams cannot be accessed", + level=1, + func=inspect.stack()[0][3], + ) + return for stream in self.stream_results: host = "" if sw is not None: host = sw.get_account().serverInfo.url - else: + else: host = self.speckle_client.account.serverInfo.url - - if isinstance(stream, SpeckleException): - logToUser("Some streams cannot be accessed", level = 1, func = inspect.stack()[0][3]) - else: - self.search_results_list.addItems([ - f"{stream.name}, {stream.id} | {host}" #for stream in self.stream_results - ]) + + if isinstance(stream, SpeckleException): + logToUser( + "Some streams cannot be accessed", + level=1, + func=inspect.stack()[0][3], + ) + else: + self.search_results_list.addItems( + [ + f"{stream.name}, {stream.id} | {host}" # for stream in self.stream_results + ] + ) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return def onOkClicked(self): try: if isinstance(self.stream_results, SpeckleException): - logToUser("Selected stream cannot be accessed: "+ str(self.stream_results.message), level = 1, func = inspect.stack()[0][3]) + logToUser( + "Selected stream cannot be accessed: " + + str(self.stream_results.message), + level=1, + func=inspect.stack()[0][3], + ) return else: try: index = self.search_results_list.currentIndex().row() stream = self.stream_results[index] item = self.search_results_list.item(index) - url = constructCommitURLfromServerCommit(item.text().split(" | ")[1], item.text().split(", ")[1].split(" | ")[0]) - #url = item.text().split(" | ")[1] + "/streams/" + item.text().split(", ")[1].split(" | ")[0] - sw = StreamWrapper(url) - - try: metrics.track("Connector Action", self.dataStorage.active_account, {"name": "Stream Add From Search", "connector_version": str(self.dataStorage.plugin_version)}) - except Exception as e: logToUser(e, level = 2, func = inspect.stack()[0][3] ) - - #acc = sw.get_account() #get_local_accounts()[self.accounts_dropdown.currentIndex()] - self.handleStreamAdd.emit((sw, self.branch_result, self.commit_result)) #StreamWrapper(f"{acc.serverInfo.url}/streams/{stream.id}?u={acc.userInfo.id}")) + url = constructCommitURLfromServerCommit( + item.text().split(" | ")[1], + item.text().split(", ")[1].split(" | ")[0], + ) + # url = item.text().split(" | ")[1] + "/streams/" + item.text().split(", ")[1].split(" | ")[0] + sw = StreamWrapper(url) + + try: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Stream Add From Search", + "connector_version": str( + self.dataStorage.plugin_version + ), + }, + ) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + + # acc = sw.get_account() #get_local_accounts()[self.accounts_dropdown.currentIndex()] + self.handleStreamAdd.emit( + (sw, self.branch_result, self.commit_result) + ) # StreamWrapper(f"{acc.serverInfo.url}/streams/{stream.id}?u={acc.userInfo.id}")) self.close() except Exception as e: - logToUser("Some streams cannot be accessed: " + str(e), level = 1, func = inspect.stack()[0][3]) - return + logToUser( + "Some streams cannot be accessed: " + str(e), + level=1, + func=inspect.stack()[0][3], + ) + return except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return def onCancelClicked(self): try: self.close() except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return - def onAccountSelected(self, index = 0): + def onAccountSelected(self, index=0): try: - - try: metrics.track("Connector Action", self.dataStorage.active_account, {"name": "Account Select", "connector_version": str(self.dataStorage.plugin_version)}) - except Exception as e: logToUser(e, level = 2, func = inspect.stack()[0][3] ) - + try: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Account Select", + "connector_version": str(self.dataStorage.plugin_version), + }, + ) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3]) + account = self.speckle_accounts[index] - self.speckle_client = SpeckleClient(account.serverInfo.url, account.serverInfo.url.startswith("https")) - self.speckle_client.authenticate_with_token(token=account.token) + self.speckle_client = SpeckleClient( + account.serverInfo.url, account.serverInfo.url.startswith("https") + ) + + try: + self.speckle_client.authenticate_with_token(token=account.token) + except SpeckleException as ex: + raise Exception(f"Dependencies versioning error: {ex.message}") + self.getAllStreams() - + except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return def populate_accounts_dropdown(self): @@ -235,6 +348,5 @@ class AddStreamModalDialog(QtWidgets.QWidget, FORM_CLASS): ] ) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return - diff --git a/qt_ui/widget_create_stream.py b/qt_ui/widget_create_stream.py index 8321fe5..e925d38 100644 --- a/qt_ui/widget_create_stream.py +++ b/qt_ui/widget_create_stream.py @@ -6,15 +6,20 @@ from specklepy_qt_ui.qt_ui.logger import logToUser from PyQt5 import QtWidgets, uic, QtCore from PyQt5.QtCore import pyqtSignal from specklepy.core.api.client import SpeckleClient -from specklepy.core.api.credentials import Account, get_local_accounts #, StreamWrapper +from specklepy.core.api.credentials import ( + Account, + get_local_accounts, +) # , StreamWrapper + +from specklepy.logging.exceptions import SpeckleException # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer FORM_CLASS, _ = uic.loadUiType( - os.path.join(os.path.join(os.path.dirname(__file__), "ui", "create_stream.ui") ) + os.path.join(os.path.join(os.path.dirname(__file__), "ui", "create_stream.ui")) ) -class CreateStreamModalDialog(QtWidgets.QWidget, FORM_CLASS): +class CreateStreamModalDialog(QtWidgets.QWidget, FORM_CLASS): name_field: QtWidgets.QLineEdit = None description_field: QtWidgets.QLineEdit = None dialog_button_box: QtWidgets.QDialogButtonBox = None @@ -23,31 +28,41 @@ class CreateStreamModalDialog(QtWidgets.QWidget, FORM_CLASS): speckle_client: Union[SpeckleClient, None] = None - #Events + # Events handleStreamCreate = pyqtSignal(Account, str, str, bool) def __init__(self, parent=None, speckle_client: SpeckleClient = None): - super(CreateStreamModalDialog,self).__init__(parent,QtCore.Qt.WindowStaysOnTopHint) + super(CreateStreamModalDialog, self).__init__( + parent, QtCore.Qt.WindowStaysOnTopHint + ) self.speckle_client = speckle_client self.setupUi(self) self.setWindowTitle("Create New Stream") self.name_field.textChanged.connect(self.nameCheck) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect(self.onOkClicked) - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Cancel).clicked.connect(self.onCancelClicked) + self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True) + self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).clicked.connect( + self.onOkClicked + ) + self.dialog_button_box.button( + QtWidgets.QDialogButtonBox.Cancel + ).clicked.connect(self.onCancelClicked) self.accounts_dropdown.currentIndexChanged.connect(self.onAccountSelected) self.populate_accounts_dropdown() def nameCheck(self): try: if len(self.name_field.text()) == 0 or len(self.name_field.text()) >= 3: - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True) - else: - self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False) + self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled( + True + ) + else: + self.dialog_button_box.button(QtWidgets.QDialogButtonBox.Ok).setEnabled( + False + ) return except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return def onOkClicked(self): @@ -56,27 +71,34 @@ class CreateStreamModalDialog(QtWidgets.QWidget, FORM_CLASS): name = self.name_field.text() description = self.description_field.text() public = self.public_toggle.isChecked() - self.handleStreamCreate.emit(acc,name,description,public) + self.handleStreamCreate.emit(acc, name, description, public) self.close() except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return def onCancelClicked(self): try: - #self.handleCancelStreamCreate.emit() + # self.handleCancelStreamCreate.emit() self.close() except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return def onAccountSelected(self, index): try: account = self.speckle_accounts[index] - self.speckle_client = SpeckleClient(account.serverInfo.url, account.serverInfo.url.startswith("https")) - self.speckle_client.authenticate_with_token(token=account.token) + self.speckle_client = SpeckleClient( + account.serverInfo.url, account.serverInfo.url.startswith("https") + ) + + try: + self.speckle_client.authenticate_with_token(token=account.token) + except SpeckleException as ex: + raise Exception(f"Dependencies versioning error: {ex.message}") + except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return def populate_accounts_dropdown(self): @@ -91,6 +113,5 @@ class CreateStreamModalDialog(QtWidgets.QWidget, FORM_CLASS): ] ) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3]) + logToUser(e, level=2, func=inspect.stack()[0][3]) return - diff --git a/qt_ui/widget_dependencies_upgrade.py b/qt_ui/widget_dependencies_upgrade.py new file mode 100644 index 0000000..f466e91 --- /dev/null +++ b/qt_ui/widget_dependencies_upgrade.py @@ -0,0 +1,98 @@ +import os +import urllib3 +import requests +import requests_toolbelt + +from PyQt5 import QtWidgets, uic, QtCore + +# This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer +FORM_CLASS, _ = uic.loadUiType( + os.path.join(os.path.join(os.path.dirname(__file__), "ui", "dependencies.ui")) +) + + +class DependenciesUpgradeDialog(QtWidgets.QWidget, FORM_CLASS): + report_label: QtWidgets.QLabel = None + report_widget: QtWidgets.QTextEdit = None + btn_cancel: QtWidgets.QPushButton = None + btn_upgrade: QtWidgets.QPushButton = None + report_text: str = "" + + def __init__(self, parent=None): + super(DependenciesUpgradeDialog, self).__init__( + parent, QtCore.Qt.WindowStaysOnTopHint + ) + self.setupUi(self) + self.runAllSetup() + + def runAllSetup(self): + self.setWindowTitle("Upgrade Python dependencies (Speckle)") + self.setMinimumWidth(900) + self.setMinimumHeight(400) + self.report_label.setWordWrap(True) + self.btn_cancel.clicked.connect(self.onOkClicked) + self.btn_upgrade.clicked.connect(self.upgradeDependencies) + self.btn_upgrade.setEnabled(True) + self.report_text = f"""Speckle plugin requires changes in versions of some Python libraries: +\nurllib3: from {urllib3.__version__} to 1.26.16 +requests: from {requests.__version__} to 2.31.0 +requests_toolbelt: from {requests_toolbelt.__version__} to 0.10.1 +\nYou can use the button below run the upgrade automatically. +To do it manually, you can run 2 following commands from QGIS Plugins panel->Python Console, and then restart QGIS: +\n\ndef upgradeDependencies(): +\timport subprocess +\tfrom speckle.utils.utils import get_qgis_python_path as path +\tresult = subprocess.run([path(), "-m", "pip", "install", "requests==2.31.0"],shell=True,timeout=1000,) +\tprint(result.returncode) +\tresult = subprocess.run([path(), "-m", "pip", "install", "urllib3==1.26.16"],shell=True,timeout=1000,) +\tprint(result.returncode) +\tresult = subprocess.run([path(), "-m", "pip", "install", "requests_toolbelt==0.10.1"],shell=True,timeout=1000,) +\tprint(result.returncode) +\nupgradeDependencies() +\n\n""" + self.report_widget.setText(self.report_text) + return + + def onOkClicked(self): + self.close() + + def upgradeDependencies(self): + self.report_widget.setText("It might take a moment...") + self.btn_upgrade.setEnabled(False) + res1, res2, res3 = self.runSubprocess() + + if res1.returncode == res2.returncode == res3.returncode == 0: + self.report_widget.setText( + "Libraries successfully upgraded. Please restart QGIS for Speckle to use the upgraded libraries." + ) + else: + self.report_widget.setText( + f"Something went wrong. Here are the error logs: \n\n{res1.stdout}\n\n{res2.stdout}" + ) + + def runSubprocess(self): + import subprocess + from speckle.utils.utils import get_qgis_python_path as path + + result1 = subprocess.run( + [path(), "-m", "pip", "install", "requests==2.31.0"], + shell=True, + timeout=1000, + capture_output=True, + text=True, + ) + result2 = subprocess.run( + [path(), "-m", "pip", "install", "urllib3==1.26.16"], + shell=True, + timeout=1000, + capture_output=True, + text=True, + ) + result3 = subprocess.run( + [path(), "-m", "pip", "install", "requests_toolbelt==0.10.1"], + shell=True, + timeout=1000, + capture_output=True, + text=True, + ) + return result1, result2, result3