diff --git a/.github/workflows/iris-wallet-desktop.yml b/.github/workflows/iris-wallet-desktop.yml
index b07ca3d..5afdb43 100644
--- a/.github/workflows/iris-wallet-desktop.yml
+++ b/.github/workflows/iris-wallet-desktop.yml
@@ -53,11 +53,6 @@ jobs:
TOKEN_URI: ${{ secrets.TOKEN_URI }}
AUTH_PROVIDER_CERT_URL: ${{ secrets.AUTH_PROVIDER_CERT_URL }}
CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
- SMTP_EMAIL_ID: ${{ secrets.SMTP_EMAIL_ID }}
- SMTP_EMAIL_TOKEN: ${{ secrets.SMTP_EMAIL_TOKEN }}
- SUPPORT_EMAIL: ${{ secrets.SUPPORT_EMAIL }}
- SMTP_HOST: ${{ secrets.SMTP_HOST }}
- SMTP_PORT: ${{ secrets.SMTP_PORT }}
run: |
cd src/utils
python generate_config.py
diff --git a/README.md b/README.md
index c01abbf..7c3c859 100644
--- a/README.md
+++ b/README.md
@@ -91,15 +91,6 @@ client_config = {
'client_secret': 'your_client_secret',
},
}
-
-# Config for the error report email server
-report_email_server_config = {
- 'smtp_email_id': 'smtp_server_email_id',
- 'smtp_email_token': 'smtp_server_email_token',
- 'support_email': 'support_email_id',
- 'smtp_host': 'smtp.gmail.com',
- 'smtp_port': '587'
-}
```
#### 8.2 Create Google Drive Credentials
@@ -139,9 +130,6 @@ report_email_server_config = {
6. **Update Your Configuration:**
- Save the modified JSON file and add it to your `config.py` file.
-#### 8.3 Create Email Server Configuration
-Search for **App Passwords** in your Gmail account to create an app password for email access.
-
### 9. Start the Application
You can now start the Iris Wallet application using:
```bash
diff --git a/src/translations/en_IN.qm b/src/translations/en_IN.qm
index 0ee3844..47bca71 100644
Binary files a/src/translations/en_IN.qm and b/src/translations/en_IN.qm differ
diff --git a/src/translations/en_IN.ts b/src/translations/en_IN.ts
index d49474b..42f9bed 100644
--- a/src/translations/en_IN.ts
+++ b/src/translations/en_IN.ts
@@ -1510,5 +1510,29 @@ If you understand the above remarks and wish to proceed, press the button below
set_minimum_confirmation_desc
Set a minimum confirmation for RGB operation
+
+ github_issue_label
+ GitHub Issue
+
+
+ email_us_at
+ Email us at:
+
+
+ do_not_expect_reply
+ Note: do not expect a reply.
+
+
+ thank_you_for_your_support
+ Thank you for your support!
+
+
+ open_an_issue
+ Open an issue on GitHub (preferred):
+
+
+ please_help_us
+ Please help us improve by sharing the logs. You can report the issue in one of the following ways:
+
diff --git a/src/utils/common_utils.py b/src/utils/common_utils.py
index e225a70..925ff2e 100644
--- a/src/utils/common_utils.py
+++ b/src/utils/common_utils.py
@@ -7,17 +7,10 @@
import base64
import binascii
import os
-import platform
import shutil
-import smtplib
import time
import zipfile
-from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
-from email import encoders
-from email.mime.base import MIMEBase
-from email.mime.multipart import MIMEMultipart
-from email.mime.text import MIMEText
from io import BytesIO
import pydenticon
@@ -42,7 +35,6 @@
from PySide6.QtWidgets import QPlainTextEdit
from PySide6.QtWidgets import QWidget
-from config import report_email_server_config
from src.data.repository.setting_repository import SettingRepository
from src.data.service.helpers.main_asset_page_helper import get_offline_asset_ticker
from src.model.enums.enums_model import AssetType
@@ -61,9 +53,7 @@
from src.utils.constant import SLOW_TRANSACTION_FEE_BLOCKS
from src.utils.custom_exception import CommonException
from src.utils.error_message import ERROR_SAVE_LOGS
-from src.utils.error_message import ERROR_SEND_REPORT_EMAIL
from src.utils.error_message import ERROR_SOMETHING_WENT_WRONG
-from src.utils.error_message import ERROR_TITLE
from src.utils.info_message import INFO_COPY_MESSAGE
from src.utils.info_message import INFO_LOG_SAVE_DESCRIPTION
from src.utils.ln_node_manage import LnNodeServerManager
@@ -472,108 +462,6 @@ def close_button_navigation(parent, back_page_navigation=None):
parent.view_model.page_navigation.settings_page()
-def generate_error_report_email(url, title):
- """Collect system info, format it, and generate the email body."""
- # Collect system information
- network = SettingRepository.get_wallet_network()
- system_info = {
- 'URL': url,
- 'OS': platform.system(),
- 'OS Version': platform.version(),
- 'Wallet Version': __version__,
- 'Wallet Network': network.value,
- 'Architecture': platform.machine(),
- 'Processor': platform.processor(),
- }
-
- # Format system information for the email report
- system_info_formatted = (
- f"System Information Report:\n"
- f"-------------------------\n"
- f"URL: {system_info['URL']}\n"
- f"Operating System: {system_info['OS']}\n"
- f"OS Version: {system_info['OS Version']}\n"
- f"Wallet Version: {system_info['Wallet Version']}\n"
- f"Wallet Network: {system_info['Wallet Network']}\n"
- f"Architecture: {system_info['Architecture']}\n"
- f"Processor: {system_info['Processor']}\n"
- )
-
- # Generate the email body
- email_body = (
- f"{title}\n"
- f"{'=' * len(title)}\n\n"
- f"{system_info_formatted}\n"
- f"Attached logs can be found in the provided ZIP file for further details."
- )
-
- return email_body
-
-
-def send_crash_report_async(email_to, subject, body, zip_file_path):
- """
- Asynchronously sends a crash report email with a ZIP attachment.
- - Initializes a thread pool to run the email sending task asynchronously.
- """
-
- def send_crash_report(email_to, subject, body, zip_file_path):
- """
- - Retrieves the email server configuration (email ID and token).
- - Creates a multipart email message with the subject, sender, and recipient.
- - Attaches the email body and the ZIP file (if it exists).
- - Connects to the SMTP server using TLS, authenticates with the email ID and token, and sends the email.
- - Handles any exceptions that occur during the email sending process by showing an error toast message.
- """
- email_id = report_email_server_config['smtp_email_id']
- email_token = report_email_server_config['smtp_email_token']
-
- # Create a multipart message
- msg = MIMEMultipart()
- msg['Subject'] = subject
- msg['From'] = email_id
- msg['To'] = email_to
-
- # Attach the body text
- msg.attach(MIMEText(body))
-
- # Attach the ZIP file
- if zip_file_path and os.path.exists(zip_file_path):
- with open(zip_file_path, 'rb') as attachment:
- part = MIMEBase('application', 'octet-stream')
- part.set_payload(attachment.read())
-
- # Encode the attachment
- encoders.encode_base64(part)
-
- # Add headers
- part.add_header(
- 'Content-Disposition',
- f'attachment; filename="{os.path.basename(zip_file_path)}"',
- )
-
- # Attach the part to the message
- msg.attach(part)
-
- try:
- # Connect to the SMTP server
- smtp_host = report_email_server_config.get(
- 'smtp_host', 'smtp.gmail.com',
- )
- smtp_port = report_email_server_config.get('smtp_port', 587)
- with smtplib.SMTP(smtp_host, smtp_port) as server:
- server.starttls()
- server.login(email_id, email_token)
- server.sendmail(email_id, [email_to], msg.as_string())
- except CommonException as e:
- ToastManager.error(
- parent=None, title=ERROR_TITLE,
- description=ERROR_SEND_REPORT_EMAIL.format(e),
- )
-
- executor = ThreadPoolExecutor(max_workers=1)
- executor.submit(send_crash_report, email_to, subject, body, zip_file_path)
-
-
def find_files_with_name(path, keyword):
"""This method finds a file using the provided name."""
found_files = []
diff --git a/src/utils/constant.py b/src/utils/constant.py
index 562b827..dabdc0a 100644
--- a/src/utils/constant.py
+++ b/src/utils/constant.py
@@ -118,3 +118,7 @@
# Syncing chain info label timer in milliseconds
SYNCING_CHAIN_LABEL_TIMER = 5000
+
+# Email and github issue url for error report
+CONTACT_EMAIL = 'iriswalletdesktop@gmail.com'
+GITHUB_ISSUE_LINK = 'https://github.com/RGB-Tools/iris-wallet-desktop/issues/new?template=Blank+issue'
diff --git a/src/utils/error_message.py b/src/utils/error_message.py
index 0e8a097..e301d92 100644
--- a/src/utils/error_message.py
+++ b/src/utils/error_message.py
@@ -46,7 +46,6 @@
ERROR_ENDPOINT_NOT_ALLOW_TO_CACHE_CHECK_CACHE_LIST = 'Provide endpoint {} not allow to cache please check cache list in endpoints.py file'
ERROR_CREATE_UTXO_FEE_RATE_ISSUE = 'Unexpected error'
ERROR_MESSAGE_TO_CHANGE_FEE_RATE = 'Please change default fee rate from setting page'
-ERROR_SEND_REPORT_EMAIL = 'Failed to send email: {}'
ERROR_OPERATION_CANCELLED = 'Operation cancelled'
ERROR_NEW_CONNECTION_ERROR = 'Failed to connect to the server.because of network connection.'
ERROR_MAX_RETRY_EXITS = 'Max retries exceeded. while checking internet connection.'
diff --git a/src/utils/generate_config.py b/src/utils/generate_config.py
index 419496c..91404fb 100644
--- a/src/utils/generate_config.py
+++ b/src/utils/generate_config.py
@@ -39,11 +39,6 @@ def get_env_var(key, default=None):
'AUTH_PROVIDER_CERT_URL', 'https://www.googleapis.com/oauth2/v1/certs',
)
client_secret = get_env_var('CLIENT_SECRET')
-smtp_email_id = get_env_var('SMTP_EMAIL_ID')
-smtp_email_token = get_env_var('SMTP_EMAIL_TOKEN')
-support_email = get_env_var('SUPPORT_EMAIL')
-smtp_host = get_env_var('SMTP_HOST', 'smtp.gmail.com')
-smtp_port = get_env_var('SMTP_PORT', '587')
# Create the content of config.py
config_content = f"""
@@ -57,14 +52,6 @@ def get_env_var(key, default=None):
'client_secret': '{client_secret}',
}},
}}
-
-report_email_server_config = {{
- 'smtp_email_id': '{smtp_email_id}',
- 'smtp_email_token': '{smtp_email_token}',
- 'support_email': '{support_email}',
- 'smtp_host': '{smtp_host}',
- 'smtp_port': '{smtp_port}'
-}}
"""
# Write the content to config.py
diff --git a/src/utils/handle_exception.py b/src/utils/handle_exception.py
index bd0eaee..bc9413c 100644
--- a/src/utils/handle_exception.py
+++ b/src/utils/handle_exception.py
@@ -38,7 +38,7 @@ def handle_exceptions(exc):
)
if exc.response.status_code == 500:
- PageNavigationEventManager.get_instance().error_report_signal.emit(exc.response.url)
+ PageNavigationEventManager.get_instance().error_report_signal.emit()
raw_exc = exc.response.json()
raise CommonException(error_message, raw_exc) from exc
diff --git a/src/utils/page_navigation.py b/src/utils/page_navigation.py
index f4e401b..9a0e084 100644
--- a/src/utils/page_navigation.py
+++ b/src/utils/page_navigation.py
@@ -411,7 +411,7 @@ def sidebar(self):
"""This method return the sidebar objects."""
return self._ui.sidebar
- def error_report_dialog_box(self, url):
+ def error_report_dialog_box(self):
"""This method display the error report dialog box"""
- error_report_dialog = ErrorReportDialog(url=url)
+ error_report_dialog = ErrorReportDialog()
error_report_dialog.exec()
diff --git a/src/utils/page_navigation_events.py b/src/utils/page_navigation_events.py
index 8916b72..e63c292 100644
--- a/src/utils/page_navigation_events.py
+++ b/src/utils/page_navigation_events.py
@@ -1,3 +1,4 @@
+# pylint: disable = too-few-public-methods
"""Make page navigation global"""
from __future__ import annotations
@@ -45,7 +46,7 @@ class PageNavigationEventManager(QObject):
about_page_signal = Signal()
faucets_page_signal = Signal()
help_page_signal = Signal()
- error_report_signal = Signal(str)
+ error_report_signal = Signal()
def __init__(self):
super().__init__()
diff --git a/src/views/components/error_report_dialog_box.py b/src/views/components/error_report_dialog_box.py
index 7a24e1d..b6900d6 100644
--- a/src/views/components/error_report_dialog_box.py
+++ b/src/views/components/error_report_dialog_box.py
@@ -1,106 +1,235 @@
-"""
-This module defines the ErrorReportDialog class, which represents a message box
-for sending error reports with translations and error details.
-"""
+# pylint: disable=too-many-instance-attributes, too-many-statements
+"""This module contains the ErrorReportDialog class which contains the UI for the error report dialog box"""
from __future__ import annotations
-import shutil
-
from PySide6.QtCore import QCoreApplication
-from PySide6.QtWidgets import QMessageBox
+from PySide6.QtCore import QSize
+from PySide6.QtCore import Qt
+from PySide6.QtGui import QCursor
+from PySide6.QtGui import QIcon
+from PySide6.QtWidgets import QDialog
+from PySide6.QtWidgets import QDialogButtonBox
+from PySide6.QtWidgets import QFileDialog
+from PySide6.QtWidgets import QGridLayout
+from PySide6.QtWidgets import QHBoxLayout
+from PySide6.QtWidgets import QLabel
+from PySide6.QtWidgets import QPushButton
+from PySide6.QtWidgets import QSizePolicy
+from PySide6.QtWidgets import QSpacerItem
+from PySide6.QtWidgets import QVBoxLayout
-from config import report_email_server_config
-from src.utils.common_utils import generate_error_report_email
-from src.utils.common_utils import send_crash_report_async
+from src.utils.common_utils import copy_text
+from src.utils.common_utils import download_file
from src.utils.common_utils import zip_logger_folder
-from src.utils.error_message import ERROR_OPERATION_CANCELLED
-from src.utils.info_message import INFO_SENDING_ERROR_REPORT
+from src.utils.constant import CONTACT_EMAIL
+from src.utils.constant import GITHUB_ISSUE_LINK
+from src.utils.helpers import load_stylesheet
from src.utils.local_store import local_store
-from src.version import __version__
-from src.views.components.toast import ToastManager
-class ErrorReportDialog(QMessageBox):
- """This class represents the error report dialog in the application."""
+class ErrorReportDialog(QDialog):
+ """This class represents the UI elements of the error report dialog"""
- def __init__(self, url, parent=None):
- """Initialize the ErrorReportDialog message box with translated strings and error details."""
- super().__init__(parent)
- self.url = url
- self.setWindowTitle('Send Error Report')
- # Create the message box
- self.setIcon(QMessageBox.Critical)
- self.setWindowTitle(
- QCoreApplication.translate(
- 'iris_wallet_desktop', 'error_report', None,
+ def __init__(self):
+ super().__init__()
+
+ self.setObjectName('error_report_dialog_box')
+ self.setStyleSheet(
+ load_stylesheet(
+ 'views/qss/error_report_dialog.qss',
),
)
+ self.grid_layout = QGridLayout(self)
+ self.grid_layout.setObjectName('grid_layout')
+ self.grid_layout.setContentsMargins(-1, -1, 33, 25)
+ self.icon_label = QLabel(self)
+ self.icon_label.setObjectName('icon_label')
+ icon = QIcon.fromTheme('dialog-error')
+ self.icon_label.setPixmap(icon.pixmap(70, 70))
+
+ self.grid_layout.addWidget(
+ self.icon_label, 0, 0, 1, 1, Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter,
+ )
+
+ self.were_sorry_label = QLabel(self)
+ self.were_sorry_label.setObjectName('were_sorry_label')
+ self.were_sorry_label.setContentsMargins(0, 15, 2, 0)
+ self.grid_layout.addWidget(self.were_sorry_label, 0, 1, 1, 2)
+
+ self.help_us_label = QLabel(self)
+ self.help_us_label.setObjectName('help_us_label')
+ self.help_us_label.setWordWrap(True)
+
+ self.grid_layout.addWidget(self.help_us_label, 1, 1, 1, 2)
+
+ self.open_issue_label = QLabel(self)
+ self.open_issue_label.setObjectName('open_issue_label')
+
+ self.grid_layout.addWidget(self.open_issue_label, 2, 1, 1, 1)
+
+ self.github_issue_label = QLabel(self)
+ self.github_issue_label.setObjectName('github_issue_label')
+
+ self.github_issue_label.setOpenExternalLinks(True)
+
+ self.grid_layout.addWidget(self.github_issue_label, 2, 2, 1, 1)
- # Fetch translations
- self.text_sorry = QCoreApplication.translate(
- 'iris_wallet_desktop', 'something_went_wrong_mb', None,
+ self.vertical_layout = QVBoxLayout()
+ self.vertical_layout.setObjectName('vertical_layout')
+ self.vertical_layout.setSpacing(0)
+ self.horizontal_layout = QHBoxLayout()
+ self.horizontal_layout.setObjectName('horizontal_layout')
+ self.horizontal_layout.setContentsMargins(-1, -1, 10, -1)
+ self.email_us_label = QLabel(self)
+ self.email_us_label.setObjectName('email_us_label')
+ self.email_us_label.setMaximumSize(QSize(100, 16777215))
+
+ self.horizontal_layout.addWidget(self.email_us_label)
+
+ self.email_label = QLabel(self)
+ self.email_label.setObjectName('email_label')
+ self.email_label.setText(CONTACT_EMAIL)
+
+ self.horizontal_layout.addWidget(
+ self.email_label, Qt.AlignmentFlag.AlignHCenter,
)
- self.text_help = QCoreApplication.translate(
- 'iris_wallet_desktop', 'error_description_mb', None,
+
+ self.copy_button = QPushButton(self)
+ self.copy_button.setObjectName('copy_button')
+ self.copy_button.setMaximumSize(QSize(16, 16))
+ self.copy_button.setStyleSheet('background:none')
+ icon = QIcon()
+ icon.addFile(
+ ':/assets/copy.png', QSize(),
+ QIcon.Mode.Normal, QIcon.State.Off,
)
- self.text_included = QCoreApplication.translate(
- 'iris_wallet_desktop', 'what_will_be_included', None,
+ self.copy_button.setIcon(icon)
+ self.copy_button.setFlat(True)
+ self.copy_button.setCursor(QCursor(Qt.CursorShape.PointingHandCursor))
+
+ self.horizontal_layout.addWidget(
+ self.copy_button, Qt.AlignmentFlag.AlignLeft,
)
- self.text_error_details = QCoreApplication.translate(
- 'iris_wallet_desktop', 'error_details_title', None,
+
+ self.horizontal_spacer = QSpacerItem(
+ 20, 20, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Fixed,
)
- self.text_app_version = QCoreApplication.translate(
- 'iris_wallet_desktop', 'application_version', None,
+
+ self.horizontal_layout.addItem(self.horizontal_spacer)
+
+ self.vertical_layout.addLayout(self.horizontal_layout)
+
+ self.no_reply_label = QLabel(self)
+ self.no_reply_label.setObjectName('no_reply_label')
+ self.vertical_layout.addWidget(
+ self.no_reply_label, Qt.AlignmentFlag.AlignTop,
)
- self.text_os_info = QCoreApplication.translate(
- 'iris_wallet_desktop', 'os_info', None,
+
+ self.grid_layout.addLayout(self.vertical_layout, 3, 1, 1, 2)
+
+ self.vertical_spacer = QSpacerItem(
+ 20, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum,
)
- self.text_send_report = QCoreApplication.translate(
- 'iris_wallet_desktop', 'error_report_permission', None,
+
+ self.grid_layout.addItem(self.vertical_spacer, 4, 1)
+
+ self.thank_you_label = QLabel(self)
+ self.thank_you_label.setObjectName('thank_you_label')
+
+ self.grid_layout.addWidget(self.thank_you_label, 5, 1, 1, 2)
+
+ self.vertical_spacer_2 = QSpacerItem(
+ 20, 20, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum,
)
- # Set the text for the message box
- self.setText(self.text_sorry)
- self.setInformativeText(
- f"{self.text_help}\n\n"
- f"{self.text_included}\n"
- f"{self.text_error_details}\n"
- f"{self.text_app_version}\n"
- f"{self.text_os_info}\n\n"
- f"{self.text_send_report}",
+ self.grid_layout.addItem(self.vertical_spacer_2, 6, 1)
+ self.button_box = QDialogButtonBox(self)
+ self.button_box.setObjectName('buttonBox')
+ self.button_box.setOrientation(Qt.Orientation.Horizontal)
+
+ self.download_debug_logs = QPushButton(self)
+ self.download_debug_logs.setObjectName('download_debug_logs')
+ self.download_debug_logs.setMinimumSize(QSize(120, 40))
+ self.download_debug_logs.setCursor(
+ QCursor(Qt.CursorShape.PointingHandCursor),
+ )
+ self.button_box.addButton(
+ self.download_debug_logs, QDialogButtonBox.ActionRole,
)
- self.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
- self.setDefaultButton(QMessageBox.Yes)
- # Connect the buttonClicked signal to a custom slot
- self.buttonClicked.connect(self.on_button_clicked)
+ self.grid_layout.addWidget(self.button_box, 7, 1, 1, 2)
- def on_button_clicked(self, button):
- """
- Handles the button click event to either send an error report or cancel the operation.
+ self.retranslate_ui()
+ self.setup_ui()
+
+ def retranslate_ui(self):
+ """translations for the error report dialog"""
+ self.setWindowTitle(
+ QCoreApplication.translate(
+ 'iris_wallet_desktop', 'error_report', None,
+ ),
+ )
+ self.were_sorry_label.setText(
+ QCoreApplication.translate(
+ 'iris_wallet_desktop', 'something_went_wrong_mb', None,
+ ),
+ )
+ self.help_us_label.setText(
+ QCoreApplication.translate(
+ 'iris_wallet_desktop', 'please_help_us', None,
+ ),
+ )
+ self.open_issue_label.setText(
+ QCoreApplication.translate(
+ 'iris_wallet_desktop', 'open_an_issue', None,
+ ),
+ )
+ self.github_issue_label.setText(
+ f"""
+ {
+ QCoreApplication.translate(
+ "iris_wallet_desktop", "github_issue_label", None
+ )
+ }
+ """,
+ )
+ self.email_us_label.setText(
+ QCoreApplication.translate(
+ 'iris_wallet_desktop', 'email_us_at', None,
+ ),
+ )
+ self.no_reply_label.setText(f"({
+ QCoreApplication.translate(
+ "iris_wallet_desktop", "do_not_expect_reply", None
+ )
+ })")
+ self.thank_you_label.setText(
+ QCoreApplication.translate(
+ 'iris_wallet_desktop', 'thank_you_for_your_support', None,
+ ),
+ )
+ self.download_debug_logs.setText(
+ QCoreApplication.translate(
+ 'iris_wallet_desktop', 'download_debug_log', None,
+ ),
+ )
- If the 'Yes' button is clicked:
- - Shows an info toast notification indicating the report is being sent.
- - Compresses the log files into a ZIP archive.
- - Prepares an error report email with the appropriate subject and body.
- - Sends the error report asynchronously to the specified email ID.
+ def setup_ui(self):
+ """Ui connections for error report dialog"""
+ self.copy_button.clicked.connect(lambda: copy_text(self.email_label))
+ self.download_debug_logs.clicked.connect(
+ self.on_click_download_debug_log,
+ )
- If the 'No' button is clicked:
- - Shows a warning toast notification indicating the operation was cancelled.
- """
- if button == self.button(QMessageBox.Yes):
- ToastManager.info(INFO_SENDING_ERROR_REPORT)
+ def on_click_download_debug_log(self):
+ """This method opens the file dialog box and saves the debug logs to the selected path"""
- base_path = local_store.get_path()
- _, output_dir = zip_logger_folder(base_path)
- zip_file_path = shutil.make_archive(output_dir, 'zip', output_dir)
+ path = local_store.get_path()
+ zip_filename, output_dir = zip_logger_folder(path)
- # Set the subject and formatted body
- subject = f"Iris Wallet Error Report - Version {__version__}"
- title = 'Error Report for Iris Wallet Desktop'
- body = generate_error_report_email(url=self.url, title=title)
- email_id = report_email_server_config['support_email']
+ save_path, _ = QFileDialog.getSaveFileName(
+ self, 'Save logs File', zip_filename, 'Zip Files (*.zip)',
+ )
- send_crash_report_async(email_id, subject, body, zip_file_path)
- elif button == self.button(QMessageBox.No):
- ToastManager.warning(ERROR_OPERATION_CANCELLED)
+ if save_path:
+ download_file(save_path, output_dir)
diff --git a/src/views/qss/error_report_dialog.qss b/src/views/qss/error_report_dialog.qss
new file mode 100644
index 0000000..f08b1e6
--- /dev/null
+++ b/src/views/qss/error_report_dialog.qss
@@ -0,0 +1,23 @@
+/* Styles for the logo title text */
+QLabel {
+ font: 16px "Inter";
+ color: white;
+}
+
+QLabel#email_label{
+ padding-left:5px;
+}
+
+QLabel#icon_label{
+ padding-top: 20px;
+ padding-left: 10px;
+ padding-right: 20px;
+}
+
+QPushButton#download_debug_logs{
+ font:16px "Inter";
+}
+
+QLabel#no_reply_label{
+ font:italic;
+}
diff --git a/unit_tests/tests/ui_tests/components/error_report_dialog_box_test.py b/unit_tests/tests/ui_tests/components/error_report_dialog_box_test.py
index cad92f3..f21d772 100644
--- a/unit_tests/tests/ui_tests/components/error_report_dialog_box_test.py
+++ b/unit_tests/tests/ui_tests/components/error_report_dialog_box_test.py
@@ -8,20 +8,21 @@
import pytest
from PySide6.QtCore import QCoreApplication
-from PySide6.QtWidgets import QMessageBox
+from PySide6.QtCore import Qt
+from PySide6.QtWidgets import QLabel
+from PySide6.QtWidgets import QPushButton
-from src.utils.error_message import ERROR_OPERATION_CANCELLED
-from src.utils.info_message import INFO_SENDING_ERROR_REPORT
from src.utils.local_store import local_store
from src.version import __version__
from src.views.components.error_report_dialog_box import ErrorReportDialog
+from src.views.components.toast import ToastManager
@pytest.fixture
def error_report_dialog(qtbot):
"""Fixture to create and return an instance of ErrorReportDialog."""
- url = 'http://example.com/error_report'
- dialog = ErrorReportDialog(url)
+ dialog = ErrorReportDialog()
+ qtbot.addWidget(dialog)
return dialog
@@ -29,105 +30,78 @@ def test_dialog_initialization(error_report_dialog):
"""Test if the ErrorReportDialog initializes with correct properties."""
dialog = error_report_dialog
+ # Test window title
expected_title = QCoreApplication.translate(
'iris_wallet_desktop', 'error_report', None,
)
assert dialog.windowTitle() == expected_title
- # Test the text in the dialog
- assert 'something_went_wrong_mb' in dialog.text_sorry
- assert 'error_description_mb' in dialog.text_help
- assert 'what_will_be_included' in dialog.text_included
- # Verify the dialog has 'Yes' and 'No' buttons
- buttons = dialog.buttons()
- assert len(buttons) == 2
- assert buttons[0].text().replace('&', '') == 'Yes'
- assert buttons[1].text().replace('&', '') == 'No'
+ # Test if main components exist
+ assert isinstance(dialog.were_sorry_label, QLabel)
+ assert isinstance(dialog.help_us_label, QLabel)
+ assert isinstance(dialog.download_debug_logs, QPushButton)
+ assert isinstance(dialog.copy_button, QPushButton)
+ # Test labels content
+ assert QCoreApplication.translate(
+ 'iris_wallet_desktop', 'something_went_wrong_mb', None,
+ ) == dialog.were_sorry_label.text()
-def test_send_report_on_yes_button(error_report_dialog):
- """Test behavior when the 'Yes' button is clicked."""
+ # Test button properties
+ assert dialog.download_debug_logs.minimumSize().width() == 120
+ assert dialog.download_debug_logs.minimumSize().height() == 40
+
+
+def test_download_debug_logs(error_report_dialog, qtbot):
+ """Test the download debug logs functionality."""
dialog = error_report_dialog
- # Mock external functions to prevent actual side effects during testing
- with patch('src.views.components.error_report_dialog_box.ToastManager.info') as mock_info, \
+ with patch('src.views.components.error_report_dialog_box.QFileDialog.getSaveFileName') as mock_file_dialog, \
patch('src.views.components.error_report_dialog_box.zip_logger_folder') as mock_zip, \
- patch('src.views.components.error_report_dialog_box.shutil.make_archive') as mock_archive, \
- patch('src.views.components.error_report_dialog_box.generate_error_report_email') as mock_generate_email, \
- patch('src.views.components.error_report_dialog_box.send_crash_report_async') as mock_send_email, \
- patch(
- 'src.views.components.error_report_dialog_box.report_email_server_config',
- {
- 'smtp_email_id': 'dummy_email_id',
- 'support_email': 'dummy_support_email',
- },
- ):
-
- # Mock return values for external functions
- mock_zip.return_value = ('dummy_dir', 'output_dir')
- mock_archive.return_value = 'dummy_path.zip'
- mock_generate_email.return_value = 'dummy email body'
-
- # Simulate a button click on "Yes"
- dialog.buttonClicked.emit(dialog.button(QMessageBox.Yes))
-
- # Verify that the correct toast message is shown
- mock_info.assert_called_once_with(INFO_SENDING_ERROR_REPORT)
-
- # Verify that the zip logger folder was called
- mock_zip.assert_called_once_with(local_store.get_path())
+ patch('src.views.components.error_report_dialog_box.download_file') as mock_download:
- # Verify that make_archive was called to create the ZIP file
- # Corrected the argument order to match the actual function call
- mock_archive.assert_called_once_with('output_dir', 'zip', 'output_dir')
+ # Mock return values
+ mock_zip.return_value = ('test.zip', 'output_dir')
+ mock_file_dialog.return_value = ('save_path.zip', 'selected_filter')
- # Verify the email generation
- mock_generate_email.assert_called_once_with(
- url='http://example.com/error_report', title='Error Report for Iris Wallet Desktop',
- )
+ # Click download button
+ qtbot.mouseClick(dialog.download_debug_logs, Qt.LeftButton)
- # Verify that the email sending function was called with correct parameters
- mock_send_email.assert_called_once_with(
- 'dummy_support_email',
- f"Iris Wallet Error Report - Version {__version__}",
- 'dummy email body',
- 'dummy_path.zip',
- )
+ # Verify zip_logger_folder was called with correct path
+ mock_zip.assert_called_once_with(local_store.get_path())
+ # Verify download_file was called with correct parameters
+ mock_download.assert_called_once_with('save_path.zip', 'output_dir')
-def test_cancel_report_on_no_button(error_report_dialog):
- """Test behavior when the 'No' button is clicked."""
+
+def test_download_debug_logs_cancelled(error_report_dialog, qtbot):
+ """Test when debug logs download is cancelled."""
dialog = error_report_dialog
- # Mock ToastManager to avoid actual toast notifications
- with patch('src.views.components.error_report_dialog_box.ToastManager.warning') as mock_warning:
- # Simulate a button click on "No"
- dialog.buttonClicked.emit(dialog.button(QMessageBox.No))
+ with patch('src.views.components.error_report_dialog_box.QFileDialog.getSaveFileName') as mock_file_dialog, \
+ patch('src.views.components.toast.ToastManager.show_toast') as mock_toast:
- # Verify the correct warning toast message is shown
- mock_warning.assert_called_once_with(ERROR_OPERATION_CANCELLED)
+ # Mock cancelled file dialog
+ mock_file_dialog.return_value = ('', '')
+ # Click download button
+ qtbot.mouseClick(dialog.download_debug_logs, Qt.LeftButton)
-def test_dialog_buttons_functionality(error_report_dialog):
- """Test if the 'Yes' and 'No' buttons work correctly."""
- dialog = error_report_dialog
+ # Verify toast was shown with correct message
+ mock_toast.assert_not_called()
- # Mock ToastManager methods to prevent actual toasts from being shown
- with patch('src.views.components.error_report_dialog_box.ToastManager.info') as mock_info, \
- patch('src.views.components.error_report_dialog_box.ToastManager.warning') as mock_warning:
- # Check 'Yes' button functionality
- yes_button = dialog.button(QMessageBox.Yes)
- assert yes_button.text().replace('&', '') == 'Yes'
- dialog.buttonClicked.emit(yes_button)
+def test_copy_button(error_report_dialog, qtbot):
+ """Test if the copy button copies email to clipboard."""
+ dialog = error_report_dialog
- # Verify that the info toast was shown
- mock_info.assert_called_once_with(INFO_SENDING_ERROR_REPORT)
+ with patch('src.views.components.error_report_dialog_box.copy_text') as mock_copy_text, \
+ patch.object(ToastManager, 'success', return_value=None):
+ # Click copy button
+ qtbot.mouseClick(dialog.copy_button, Qt.LeftButton)
- # Check 'No' button functionality
- no_button = dialog.button(QMessageBox.No)
- assert no_button.text().replace('&', '') == 'No'
- dialog.buttonClicked.emit(no_button)
+ # Process events to allow click to propagate
+ qtbot.wait(100)
- # Verify that the warning toast was shown
- mock_warning.assert_called_once_with(ERROR_OPERATION_CANCELLED)
+ # Verify copy_text was called with correct label
+ mock_copy_text.assert_called_once_with(dialog.email_label)
diff --git a/unit_tests/tests/utils_test/common_utils_test.py b/unit_tests/tests/utils_test/common_utils_test.py
index d8ec881..e61194e 100644
--- a/unit_tests/tests/utils_test/common_utils_test.py
+++ b/unit_tests/tests/utils_test/common_utils_test.py
@@ -1,6 +1,6 @@
# Disable the redefined-outer-name warning as
# it's normal to pass mocked object in tests function
-# pylint: disable=redefined-outer-name,unused-argument, too-many-lines, no-value-for-parameter
+# pylint: disable=redefined-outer-name,unused-argument, too-many-lines, no-value-for-parameter, use-implicit-booleaness-not-comparison
"""unit tests for common utils"""
from __future__ import annotations
@@ -26,14 +26,12 @@
from src.model.enums.enums_model import NetworkEnumModel
from src.model.enums.enums_model import TokenSymbol
from src.model.selection_page_model import SelectionPageModel
-from src.utils.constant import DEFAULT_LOCALE
from src.utils.common_utils import close_button_navigation
from src.utils.common_utils import convert_hex_to_image
from src.utils.common_utils import convert_timestamp
from src.utils.common_utils import copy_text
from src.utils.common_utils import download_file
from src.utils.common_utils import find_files_with_name
-from src.utils.common_utils import generate_error_report_email
from src.utils.common_utils import generate_identicon
from src.utils.common_utils import get_bitcoin_info_by_network
from src.utils.common_utils import load_translator
@@ -44,6 +42,7 @@
from src.utils.common_utils import translate_value
from src.utils.common_utils import zip_logger_folder
from src.utils.constant import APP_NAME
+from src.utils.constant import DEFAULT_LOCALE
from src.utils.constant import LOG_FOLDER_NAME
from src.utils.custom_exception import CommonException
from src.version import __version__
@@ -259,10 +258,6 @@ def test_convert_hex_to_image_valid_data():
pixmap = convert_hex_to_image(valid_hex)
assert isinstance(pixmap, QPixmap)
-# import time
-# from unittest.mock import patch
-# import os
-
def test_zip_logger_folder():
"""
@@ -943,52 +938,6 @@ def test_resize_image_file_not_found(mock_exists):
) == 'The file /mock/path/nonexistent.png does not exist.'
-def test_generate_error_report_email(mocker):
- """Test generate_error_report_email function"""
- # Mock all dependencies
- mocker.patch(
- 'src.data.repository.setting_repository.SettingRepository.get_wallet_network',
- return_value=mocker.MagicMock(value='Mainnet'),
- )
- mocker.patch('platform.system', return_value='Linux')
- mocker.patch('platform.version', return_value='5.4.0-104-generic')
- mocker.patch('platform.machine', return_value='x86_64')
- mocker.patch(
- 'platform.processor',
- return_value='Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz',
- )
- mocker.patch('src.version.__version__', '0.0.6')
-
- # Mock inputs
- url = 'http://example.com/error'
- title = 'Error Report'
-
- # Call the function
- result = generate_error_report_email(url, title)
-
- # Assert the result matches the expected format
- expected_system_info = (
- f"System Information Report:\n"
- f"-------------------------\n"
- f"URL: {url}\n"
- f"Operating System: Linux\n"
- f"OS Version: 5.4.0-104-generic\n"
- f"Wallet Version: 0.0.6\n"
- f"Wallet Network: Mainnet\n"
- f"Architecture: x86_64\n"
- f"Processor: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz\n"
- )
-
- expected_email_body = (
- f"{title}\n"
- f"{'=' * len(title)}\n\n"
- f"{expected_system_info}\n"
- f"Attached logs can be found in the provided ZIP file for further details."
- )
-
- assert result == expected_email_body
-
-
@patch('PySide6.QtWidgets.QMessageBox.warning')
@patch('src.utils.ln_node_manage.LnNodeServerManager.get_instance')
@patch('PySide6.QtWidgets.QApplication.instance')
diff --git a/unit_tests/tests/utils_test/handle_exception_test.py b/unit_tests/tests/utils_test/handle_exception_test.py
index 1d290ae..0a86238 100644
--- a/unit_tests/tests/utils_test/handle_exception_test.py
+++ b/unit_tests/tests/utils_test/handle_exception_test.py
@@ -49,13 +49,13 @@ def test_http_error_500_with_error_report():
with patch('src.utils.handle_exception.PageNavigationEventManager') as mock_manager:
mock_instance = MagicMock()
mock_manager.get_instance.return_value = mock_instance
+ mock_instance.error_report_signal = MagicMock()
with pytest.raises(CommonException) as exc_info:
handle_exceptions(exc)
- mock_instance.error_report_signal.emit.assert_called_once_with(
- 'http://test.url',
- )
+ # Verify error_report_signal.emit() was called with no arguments
+ mock_instance.error_report_signal.emit.assert_called_once_with()
assert str(exc_info.value) == 'Server error'
diff --git a/unit_tests/tests/utils_test/page_navigation_test.py b/unit_tests/tests/utils_test/page_navigation_test.py
index 3799a8b..4389902 100644
--- a/unit_tests/tests/utils_test/page_navigation_test.py
+++ b/unit_tests/tests/utils_test/page_navigation_test.py
@@ -301,16 +301,15 @@ def test_sidebar(page_navigation, mock_ui):
def test_error_report_dialog_box(page_navigation):
"""Test error_report_dialog_box method."""
- url = 'test_url'
with patch('src.utils.page_navigation.ErrorReportDialog') as mock_dialog:
mock_dialog_instance = MagicMock()
mock_dialog.return_value = mock_dialog_instance
# Call the method we're testing
- page_navigation.error_report_dialog_box(url)
+ page_navigation.error_report_dialog_box()
- # Verify the dialog was created with correct URL
- mock_dialog.assert_called_once_with(url=url)
+ # Verify the dialog was created
+ mock_dialog.assert_called_once()
# Verify the dialog was shown
mock_dialog_instance.exec.assert_called_once()