diff --git a/res/pyinstaller/create_application.py b/res/pyinstaller/create_application.py index 8cab7fd8..efcd6358 100644 --- a/res/pyinstaller/create_application.py +++ b/res/pyinstaller/create_application.py @@ -8,7 +8,7 @@ import PyInstaller.__main__ from mapclient.core.provenance import reproducibility_info -from mapclient.settings.definitions import FROZEN_PROVENANCE_INFO_FILE +from mapclient.settings.definitions import APPLICATION_NAME, FROZEN_PROVENANCE_INFO_FILE # Set Python optimisations on. @@ -20,7 +20,7 @@ def main(variant): run_command = [ '../../src/mapclient/application.py', - '-n', f'MAP-Client{variant}', + '-n', f'{APPLICATION_NAME}{variant}', # '--debug', 'noarchive', '--windowed', # '--console', diff --git a/src/mapclient/core/utils.py b/src/mapclient/core/utils.py index ffbccd8d..a3d2635f 100644 --- a/src/mapclient/core/utils.py +++ b/src/mapclient/core/utils.py @@ -25,7 +25,7 @@ from subprocess import Popen, PIPE, DEVNULL import PySide6 as RefMod -from mapclient.settings.definitions import PLUGINS_PACKAGE_NAME +from mapclient.settings.definitions import APPLICATION_NAME, PLUGINS_PACKAGE_NAME from mapclient.settings.general import get_configuration_file @@ -33,6 +33,20 @@ def is_frozen(): return getattr(sys, 'frozen', False) +def is_mapping_tools(): + variant = get_map_client_variant() + return variant == "mapping-tools" + + +def get_map_client_variant(): + application_name = os.path.basename(sys.executable) + pattern = r'{}-(.*).exe'.format(APPLICATION_NAME) + match = re.search(pattern, application_name) + variant = match.group(1) if match else "" + + return variant + + def convertExceptionToMessage(e): string_e = str(e) if '\n' in string_e: diff --git a/src/mapclient/settings/definitions.py b/src/mapclient/settings/definitions.py index cde6a2ff..a42f3521 100644 --- a/src/mapclient/settings/definitions.py +++ b/src/mapclient/settings/definitions.py @@ -25,6 +25,7 @@ keep the strings the same, obviously it doesn't help with the side-effects of changing the string. """ +APPLICATION_NAME = "MAP-Client" # Options related strings OPTIONS_SETTINGS_TAG = 'Options' diff --git a/src/mapclient/view/dialogs/error/__init__.py b/src/mapclient/view/dialogs/error/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/mapclient/view/dialogs/error/errordialog.py b/src/mapclient/view/dialogs/error/errordialog.py new file mode 100644 index 00000000..7bd5faf1 --- /dev/null +++ b/src/mapclient/view/dialogs/error/errordialog.py @@ -0,0 +1,44 @@ + +from PySide6 import QtWidgets + +from mapclient.view.dialogs.reportissue.utils import create_github_issue + + +class ErrorDialog(QtWidgets.QDialog): + def __init__(self, title, text, parent=None): + super().__init__(parent=parent) + self._text = text + + critical_icon = QtWidgets.QMessageBox.standardIcon(QtWidgets.QMessageBox.Icon.Critical) + layout = QtWidgets.QVBoxLayout() + + # Create and add the icon. + critical_icon_label = QtWidgets.QLabel() + critical_icon_label.setPixmap(critical_icon) + layout.addWidget(critical_icon_label) + + # Create text. + label = QtWidgets.QLabel(text) + label.setWordWrap(True) + layout.addWidget(label) + + # Create buttons. + button_layout = QtWidgets.QHBoxLayout() + github_issue_button = QtWidgets.QPushButton("Submit GitHub Issue") + spacer = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Policy.Expanding) + ok_button = QtWidgets.QPushButton("OK") + button_layout.addWidget(github_issue_button) + button_layout.addSpacerItem(spacer) + button_layout.addWidget(ok_button) + layout.addLayout(button_layout) + ok_button.clicked.connect(self.accept) + github_issue_button.clicked.connect(self._create_github_issue) + + self.setWindowTitle(title) + self.setWindowIcon(critical_icon) + self.setLayout(layout) + + def _create_github_issue(self): + text = self._text.replace("\n", "%0A") + text = "%0A%0A```%0A" + text + "```" + create_github_issue(text) diff --git a/src/mapclient/view/dialogs/reportissue/__init__.py b/src/mapclient/view/dialogs/reportissue/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/mapclient/view/dialogs/reportissue/qt/reportissuedialog.ui b/src/mapclient/view/dialogs/reportissue/qt/reportissuedialog.ui new file mode 100644 index 00000000..f30c5329 --- /dev/null +++ b/src/mapclient/view/dialogs/reportissue/qt/reportissuedialog.ui @@ -0,0 +1,168 @@ + + + ReportIssueDialog + + + + 0 + 0 + 650 + 400 + + + + Report Issue + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + <p align="center"><span style=" font-size:14pt; font-weight:600;">Issue Reporting</span></p> + +<p align="justify">If you have encountered an issue with the Mapping-Tools, please consider submitting an issue report using one of the following methods:</p> + +<p align="justify" style="margin-left:20; margin-right:20;"> + +If possible, we recommend submitting an issue in the <a href="https://github.com/MusculoskeletalAtlasProject/mapclient">MAP-Client</a> GitHub repository. This is our primary location for all issues related to the MAP-Client and Mapping-Tools. Once you have submitted an issue, the MAP-Client developers will be notified and you can use the issue page to track any progress made in response to your submission. Note that you will need a GitHub account for this method. + +</p> + + + + Qt::AlignHCenter|Qt::AlignTop + + + true + + + + + + + <p align="justify" style="margin-left:20; margin-right:20;"> + +If you are a member of the SPARC Wrike group, you may also wish to submit a request for a Wrike ticket using the link provided below. + +</p> + + + + true + + + + + + + <p align="justify" style="margin-left:20; margin-right:20;"> + +Alternatively, it is also possible to report an issue by email if you'd prefer. Please direct your messages to <i>mapping-tools-support@sparc.science</i> for any issues or questions you have in regards to the MAP-Client. + +<br/></p> + +<p align="justify">Make sure to provide a description of the issue or error that you have encountered, including any steps that may be required to reproduce it. If possible, please also include any relevant error messages. Note that the MAP-Client keeps a log of the current session if you need to review any error messages or related application events. A copy of this log can be accessed within the MAP-Client under <i>View -> Log Information</i>. If the error occurred during the execution of a plugin step, please include the name of the plugin in your report.</p> + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + GitHub Issue + + + + + + + Wrike Ticket + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + + + + + + + + + + close_button + clicked() + ReportIssueDialog + close() + + + 530 + 362 + + + 299 + 199 + + + + + diff --git a/src/mapclient/view/dialogs/reportissue/reportissuedialog.py b/src/mapclient/view/dialogs/reportissue/reportissuedialog.py new file mode 100644 index 00000000..4b75ded9 --- /dev/null +++ b/src/mapclient/view/dialogs/reportissue/reportissuedialog.py @@ -0,0 +1,31 @@ +from PySide6 import QtWidgets + +from mapclient.core.utils import is_mapping_tools +from mapclient.view.dialogs.reportissue.utils import create_github_issue, create_wrike_ticket +from mapclient.view.dialogs.reportissue.ui.ui_reportissuedialog import Ui_ReportIssueDialog + + +class ReportIssueDialog(QtWidgets.QDialog): + """ + Dialog with instructions on how and where to report issues in the MAP-Client. + """ + + def __init__(self, parent=None): + QtWidgets.QDialog.__init__(self, parent) + self._ui = Ui_ReportIssueDialog() + self._ui.setupUi(self) + self._make_connections() + self._check_variant() + + def _make_connections(self): + self._ui.github_issue_button.clicked.connect(_create_github_issue) + self._ui.wrike_ticket_button.clicked.connect(create_wrike_ticket) + + def _check_variant(self): + if not is_mapping_tools(): + self._ui.wrike_description.hide() + self._ui.wrike_ticket_button.hide() + + +def _create_github_issue(): + create_github_issue() diff --git a/src/mapclient/view/dialogs/reportissue/ui/ui_reportissuedialog.py b/src/mapclient/view/dialogs/reportissue/ui/ui_reportissuedialog.py new file mode 100644 index 00000000..d0cadad7 --- /dev/null +++ b/src/mapclient/view/dialogs/reportissue/ui/ui_reportissuedialog.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'reportissuedialog.ui' +## +## Created by: Qt User Interface Compiler version 6.5.1 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QDialog, QFrame, QHBoxLayout, + QLabel, QPushButton, QSizePolicy, QSpacerItem, + QVBoxLayout, QWidget) + +class Ui_ReportIssueDialog(object): + def setupUi(self, ReportIssueDialog): + if not ReportIssueDialog.objectName(): + ReportIssueDialog.setObjectName(u"ReportIssueDialog") + ReportIssueDialog.resize(650, 400) + self.verticalLayout = QVBoxLayout(ReportIssueDialog) + self.verticalLayout.setObjectName(u"verticalLayout") + self.frame_2 = QFrame(ReportIssueDialog) + self.frame_2.setObjectName(u"frame_2") + self.frame_2.setFrameShape(QFrame.StyledPanel) + self.frame_2.setFrameShadow(QFrame.Raised) + self.verticalLayout_2 = QVBoxLayout(self.frame_2) + self.verticalLayout_2.setObjectName(u"verticalLayout_2") + self.description = QLabel(self.frame_2) + self.description.setObjectName(u"description") + self.description.setAlignment(Qt.AlignHCenter|Qt.AlignTop) + self.description.setWordWrap(True) + + self.verticalLayout_2.addWidget(self.description) + + self.wrike_description = QLabel(self.frame_2) + self.wrike_description.setObjectName(u"wrike_description") + self.wrike_description.setWordWrap(True) + + self.verticalLayout_2.addWidget(self.wrike_description) + + self.description_2 = QLabel(self.frame_2) + self.description_2.setObjectName(u"description_2") + self.description_2.setWordWrap(True) + + self.verticalLayout_2.addWidget(self.description_2) + + self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) + + self.verticalLayout_2.addItem(self.verticalSpacer) + + + self.verticalLayout.addWidget(self.frame_2) + + self.frame = QFrame(ReportIssueDialog) + self.frame.setObjectName(u"frame") + sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth()) + self.frame.setSizePolicy(sizePolicy) + self.frame.setFrameShape(QFrame.StyledPanel) + self.frame.setFrameShadow(QFrame.Raised) + self.horizontalLayout = QHBoxLayout(self.frame) + self.horizontalLayout.setObjectName(u"horizontalLayout") + self.github_issue_button = QPushButton(self.frame) + self.github_issue_button.setObjectName(u"github_issue_button") + + self.horizontalLayout.addWidget(self.github_issue_button) + + self.wrike_ticket_button = QPushButton(self.frame) + self.wrike_ticket_button.setObjectName(u"wrike_ticket_button") + + self.horizontalLayout.addWidget(self.wrike_ticket_button) + + self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + + self.horizontalLayout.addItem(self.horizontalSpacer) + + self.close_button = QPushButton(self.frame) + self.close_button.setObjectName(u"close_button") + + self.horizontalLayout.addWidget(self.close_button) + + + self.verticalLayout.addWidget(self.frame) + + + self.retranslateUi(ReportIssueDialog) + self.close_button.clicked.connect(ReportIssueDialog.close) + + QMetaObject.connectSlotsByName(ReportIssueDialog) + # setupUi + + def retranslateUi(self, ReportIssueDialog): + ReportIssueDialog.setWindowTitle(QCoreApplication.translate("ReportIssueDialog", u"Report Issue", None)) + self.description.setText(QCoreApplication.translate("ReportIssueDialog", u"

Issue Reporting

\n" +"\n" +"

If you have encountered an issue with the Mapping-Tools, please consider submitting an issue report using one of the following methods:

\n" +"\n" +"

\n" +"\n" +"If possible, we recommend submitting an issue in the MAP-Client GitHub repository. This is our primary location for all issues related to the MAP-Client and Mapping-Tools. Once you have submitted an issue, the MAP-Client developers will be notified and you can use the issue page to track any progress made in response to your submission. Note that you will need a GitHub account for this method.\n" +"\n" +"

\n" +"", None)) + self.wrike_description.setText(QCoreApplication.translate("ReportIssueDialog", u"

\n" +"\n" +"If you are a member of the SPARC Wrike group, you may also wish to submit a request for a Wrike ticket using the link provided below.\n" +"\n" +"

\n" +"", None)) + self.description_2.setText(QCoreApplication.translate("ReportIssueDialog", u"

\n" +"\n" +"Alternatively, it is also possible to report an issue by email if you'd prefer. Please direct your messages to mapping-tools-support@sparc.science for any issues or questions you have in regards to the MAP-Client.\n" +"\n" +"

\n" +"\n" +"

Make sure to provide a description of the issue or error that you have encountered, including any steps that may be required to reproduce it. If possible, please also include any relevant error messages. Note that the MAP-Client keeps a log of the current session if you need to review any error messages or related application events. A copy of this log can be accessed within the MAP-Client under View -> Log Information. If the error occurred during the execution of a plugin step, please include the name of the plugin in your report.

", None)) + self.github_issue_button.setText(QCoreApplication.translate("ReportIssueDialog", u"GitHub Issue", None)) + self.wrike_ticket_button.setText(QCoreApplication.translate("ReportIssueDialog", u"Wrike Ticket", None)) + self.close_button.setText(QCoreApplication.translate("ReportIssueDialog", u"Close", None)) + # retranslateUi + diff --git a/src/mapclient/view/dialogs/reportissue/utils.py b/src/mapclient/view/dialogs/reportissue/utils.py new file mode 100644 index 00000000..5a7f2eef --- /dev/null +++ b/src/mapclient/view/dialogs/reportissue/utils.py @@ -0,0 +1,16 @@ +import webbrowser + + +def create_github_issue(text=""): + """ + Open the url for creating a new GitHub issue in the MAP-Client repository and auto-fill the issue description with the text supplied. + """ + url = "https://github.com/MusculoskeletalAtlasProject/mapclient/issues/new" + url += "?body=" + text + webbrowser.open(url) + + +def create_wrike_ticket(): + url = "https://www.wrike.com/frontend/requestforms/index.html?token=eyJhY2NvdW50SWQiOjMyMDM1ODgsInRhc2tGb3JtSWQiOjU5NTQxOH0JNDg0N" \ + "zEzOTAxODIxNgllZTg2ZDg5NmNkMDQ3YjJkMWM2Njg5ZWI5YTQ2NjMxNGJkZjZiOWQwYjZkZmU2NTk1YzU0MWVlN2EyZjQ5M2Ux" + webbrowser.open(url) diff --git a/src/mapclient/view/mainwindow.py b/src/mapclient/view/mainwindow.py index 43624d71..7576c0ad 100644 --- a/src/mapclient/view/mainwindow.py +++ b/src/mapclient/view/mainwindow.py @@ -93,6 +93,8 @@ def _setup_menus(self): self._action_Options.setObjectName("action_Options") self._action_About = QtGui.QAction(self) self._action_About.setObjectName("action_About") + self._action_ReportIssue = QtGui.QAction(self) + self._action_ReportIssue.setObjectName("_action_ReportIssue") self._action_Quit = QtGui.QAction(self) self._action_Quit.setObjectName("action_Quit") self._action_PluginManager = QtGui.QAction(self) @@ -114,6 +116,7 @@ def _setup_menus(self): self._action_MAPIcon.setObjectName("actionMAPIcon") self._menu_Help.addAction(self._action_About) + self._menu_Help.addAction(self._action_ReportIssue) self._menu_View.addSeparator() self._menu_View.addAction(self._action_LogInformation) self._menu_View.addAction(self._action_Options) @@ -145,6 +148,7 @@ def _re_translate_ui(self): self._menu_Workflow.setTitle(QtWidgets.QApplication.translate("MainWindow", "&Workflow", None, -1)) self._menu_Tools.setTitle(QtWidgets.QApplication.translate("MainWindow", "&Tools", None, -1)) self._action_About.setText(QtWidgets.QApplication.translate("MainWindow", "&About", None, -1)) + self._action_ReportIssue.setText(QtWidgets.QApplication.translate("MainWindow", "&Report Issue", None, -1)) self._action_Quit.setText(QtWidgets.QApplication.translate("MainWindow", "&Quit", None, -1)) self._action_Quit.setStatusTip(QtWidgets.QApplication.translate("MainWindow", "Quit the application", None, -1)) self._action_Quit.setShortcut(QtWidgets.QApplication.translate("MainWindow", "Ctrl+Q", None, -1)) @@ -197,6 +201,7 @@ def model(self): def _make_connections(self): self._action_Quit.triggered.connect(self.quit_application) self._action_About.triggered.connect(self.about) + self._action_ReportIssue.triggered.connect(self.report_issue) self._action_LogInformation.triggered.connect(self._show_log_information_dialog) self._action_Options.triggered.connect(self.show_options_dialog) self._action_PluginManager.triggered.connect(self._show_plugin_manager_dialog) @@ -316,6 +321,12 @@ def about(self): dlg.setModal(True) dlg.exec() + def report_issue(self): + from mapclient.view.dialogs.reportissue.reportissuedialog import ReportIssueDialog + dlg = ReportIssueDialog(self) + dlg.setModal(True) + dlg.exec() + def _show_log_information_dialog(self): from mapclient.view.dialogs.log.loginformation import LogInformation dlg = LogInformation(self) diff --git a/src/mapclient/view/utils.py b/src/mapclient/view/utils.py index ded0d6ce..3f5687d2 100644 --- a/src/mapclient/view/utils.py +++ b/src/mapclient/view/utils.py @@ -23,6 +23,7 @@ from PySide6 import QtCore, QtGui, QtWidgets from mapclient.exceptions import ClientRuntimeError +from mapclient.view.dialogs.error.errordialog import ErrorDialog logger = logging.getLogger(__name__) @@ -99,5 +100,5 @@ def do_runtime_error(self, *a, **kw): return f(self, *a, **kw) except ClientRuntimeError as e: logger.error('{0}: {1}'.format(e.title, e.description)) - QtWidgets.QMessageBox.critical(self, e.title, e.description) + ErrorDialog(e.title, e.description, self).exec() return do_runtime_error