Add ability to send a bug report from within the app
This commit is contained in:
parent
6bc5b66d3f
commit
eedf48dc6a
9 changed files with 345 additions and 25 deletions
131
bouquin/bug_report_dialog.py
Normal file
131
bouquin/bug_report_dialog.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import importlib.metadata
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QTextEdit,
|
||||
QDialogButtonBox,
|
||||
QMessageBox,
|
||||
)
|
||||
|
||||
from . import strings
|
||||
|
||||
|
||||
BUG_REPORT_HOST = "https://nr.mig5.net"
|
||||
ROUTE = "forms/bouquin/bugs"
|
||||
|
||||
|
||||
class BugReportDialog(QDialog):
|
||||
"""
|
||||
Dialog to collect a bug report
|
||||
"""
|
||||
|
||||
MAX_CHARS = 5000
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle(strings._("report_a_bug"))
|
||||
|
||||
self._attachment_path: Path | None = None
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
header = QLabel(strings._("bug_report_explanation"))
|
||||
header.setWordWrap(True)
|
||||
layout.addWidget(header)
|
||||
|
||||
self.text_edit = QTextEdit()
|
||||
self.text_edit.setPlaceholderText(strings._("bug_report_placeholder"))
|
||||
layout.addWidget(self.text_edit)
|
||||
|
||||
self.text_edit.textChanged.connect(self._enforce_max_length)
|
||||
|
||||
# Buttons: Cancel / Send
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.Cancel)
|
||||
self.send_button = button_box.addButton(
|
||||
strings._("send"), QDialogButtonBox.AcceptRole
|
||||
)
|
||||
button_box.accepted.connect(self._send)
|
||||
button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(button_box)
|
||||
|
||||
self.text_edit.setFocus()
|
||||
|
||||
# ------------Helpers ------------ #
|
||||
|
||||
def _enforce_max_length(self):
|
||||
text = self.text_edit.toPlainText()
|
||||
if len(text) <= self.MAX_CHARS:
|
||||
return
|
||||
|
||||
# Remember cursor position
|
||||
cursor = self.text_edit.textCursor()
|
||||
pos = cursor.position()
|
||||
|
||||
# Trim and restore without re-entering this slot
|
||||
self.text_edit.blockSignals(True)
|
||||
self.text_edit.setPlainText(text[: self.MAX_CHARS])
|
||||
self.text_edit.blockSignals(False)
|
||||
|
||||
# Clamp cursor position to end of text
|
||||
if pos > self.MAX_CHARS:
|
||||
pos = self.MAX_CHARS
|
||||
|
||||
cursor.setPosition(pos)
|
||||
self.text_edit.setTextCursor(cursor)
|
||||
|
||||
def _send(self):
|
||||
text = self.text_edit.toPlainText().strip()
|
||||
if not text:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
strings._("report_a_bug"),
|
||||
strings._("bug_report_empty"),
|
||||
)
|
||||
return
|
||||
|
||||
# Get current app version
|
||||
try:
|
||||
version = importlib.metadata.version("bouquin")
|
||||
except importlib.metadata.PackageNotFoundError:
|
||||
version = "unknown"
|
||||
|
||||
payload: dict[str, str] = {
|
||||
"message": text,
|
||||
"version": version,
|
||||
}
|
||||
|
||||
# POST as JSON
|
||||
try:
|
||||
resp = requests.post(
|
||||
f"{BUG_REPORT_HOST}/{ROUTE}",
|
||||
json=payload,
|
||||
timeout=10,
|
||||
)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
strings._("report_a_bug"),
|
||||
strings._("bug_report_send_failed") + f"\n{e}",
|
||||
)
|
||||
return
|
||||
|
||||
if resp.status_code == 201:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
strings._("report_a_bug"),
|
||||
strings._("bug_report_sent_ok"),
|
||||
)
|
||||
self.accept()
|
||||
else:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
strings._("report_a_bug"),
|
||||
strings._("bug_report_send_failed") + f" (HTTP {resp.status_code})",
|
||||
)
|
||||
|
|
@ -148,6 +148,11 @@
|
|||
"stats_metric_words": "Words",
|
||||
"stats_metric_revisions": "Revisions",
|
||||
"stats_no_data": "No statistics available yet.",
|
||||
"select_notebook": "Select notebook"
|
||||
|
||||
"select_notebook": "Select notebook",
|
||||
"bug_report_explanation": "Describe what went wrong, what you expected to happen, and any steps to reproduce.\n\nWe do not collect anything else except the Bouquin version number.\n\nIf you wish to be contacted, please leave contact information.\n\nYour request will be sent over HTTPS.",
|
||||
"bug_report_placeholder": "Type your bug report here",
|
||||
"bug_report_empty": "Please enter some details about the bug before sending.",
|
||||
"bug_report_send_failed": "Could not send bug report.",
|
||||
"bug_report_sent_ok": "Bug report sent. Thank you!",
|
||||
"send": "Send"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ from .search import Search
|
|||
from .settings import APP_ORG, APP_NAME, load_db_config, save_db_config
|
||||
from .settings_dialog import SettingsDialog
|
||||
from .statistics_dialog import StatisticsDialog
|
||||
from .bug_report_dialog import BugReportDialog
|
||||
from . import strings
|
||||
from .tags_widget import PageTagsWidget
|
||||
from .toolbar import ToolBar
|
||||
|
|
@ -1292,14 +1293,8 @@ class MainWindow(QMainWindow):
|
|||
)
|
||||
|
||||
def _open_bugs(self):
|
||||
url_str = "https://nr.mig5.net/forms/mig5/contact"
|
||||
url = QUrl.fromUserInput(url_str)
|
||||
if not QDesktopServices.openUrl(url):
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
strings._("report_a_bug"),
|
||||
strings._("couldnt_open") + url.toDisplayString(),
|
||||
)
|
||||
dlg = BugReportDialog(self)
|
||||
dlg.exec()
|
||||
|
||||
def _open_version(self):
|
||||
version = importlib.metadata.version("bouquin")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue